mirror of
https://github.com/AssetRipper/AssetRipper.TextureDecoder.git
synced 2026-11-12 18:07:28 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbfe5b2979 | ||
|
|
168098c29e | ||
|
|
8b493e5650 | ||
|
|
9e60f004df | ||
|
|
324f8eb8f4 | ||
|
|
b39200e3ff | ||
|
|
ed8ffde7d0 | ||
|
|
e70c6d961c | ||
|
|
93ad0eeccf | ||
|
|
b5b6b5aca8 | ||
|
|
2c2f9486f9 | ||
|
|
cd60c44ba5 | ||
|
|
09f605e45d | ||
|
|
40ef1f6989 | ||
|
|
eeffa5897d | ||
|
|
d45e129e46 | ||
|
|
d57e3461ff | ||
|
|
1d2d19c9ef | ||
|
|
f3ba84589d | ||
|
|
7cee390b2e | ||
|
|
024ac9eb8c | ||
|
|
d47fe4dcfb | ||
|
|
7d028ce3f5 | ||
|
|
beb819daa4 | ||
|
|
c9d338f159 | ||
|
|
19b2469595 | ||
|
|
c5a2382733 | ||
|
|
9ce8311e1b | ||
|
|
e39b439ccd | ||
|
|
f2da43bcbb | ||
|
|
128eb58864 | ||
|
|
9f7a61172a | ||
|
|
93c177232f | ||
|
|
8b62957cdb | ||
|
|
95ecf5b1ac | ||
|
|
f8d17ad290 |
No files matched your search
@@ -12,11 +12,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4.0.0
|
||||
uses: actions/setup-dotnet@v5.0.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 9.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" />
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -52,43 +52,40 @@ internal static partial class Program
|
||||
DecodeRgb(data, width, height, args5, bitmap.Bits);
|
||||
break;
|
||||
case "yuy2":
|
||||
Yuy2Decoder.DecompressYUY2(data, width, height, bitmap.Bits);
|
||||
Yuy2Decoder.DecompressYUY2<ColorBGRA32, byte>(data, width, height, bitmap.Bits);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(inputType);
|
||||
}
|
||||
|
||||
string dirPath = Path.GetDirectoryName(path) ?? Environment.CurrentDirectory;
|
||||
string name = Path.GetFileName(path);
|
||||
|
||||
switch (outputType)
|
||||
{
|
||||
case "bgra":
|
||||
WriteAllBytes(Path.Combine(dirPath, name + ".bgra"), bitmap.Bits);
|
||||
File.WriteAllBytes(path + ".bgra", bitmap.Bits);
|
||||
break;
|
||||
case "bmp":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsBmp(Path.Combine(dirPath, name + ".bmp"));
|
||||
bitmap.SaveAsBmp(path + ".bmp");
|
||||
break;
|
||||
case "exr":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsExr(Path.Combine(dirPath, name + ".exr"));
|
||||
bitmap.SaveAsExr(path + ".exr");
|
||||
break;
|
||||
case "hdr":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsHdr(Path.Combine(dirPath, name + ".hdr"));
|
||||
bitmap.SaveAsHdr(path + ".hdr");
|
||||
break;
|
||||
case "jpg":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsJpg(Path.Combine(dirPath, name + ".jpg"));
|
||||
bitmap.SaveAsJpg(path + ".jpg");
|
||||
break;
|
||||
case "png":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsPng(Path.Combine(dirPath, name + ".png"));
|
||||
bitmap.SaveAsPng(path + ".png");
|
||||
break;
|
||||
case "tga":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsTga(Path.Combine(dirPath, name + ".tga"));
|
||||
bitmap.SaveAsTga(path + ".tga");
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(outputType);
|
||||
@@ -108,7 +105,7 @@ internal static partial class Program
|
||||
Console.WriteLine("Arg at index 6 : blockYSize");
|
||||
int blockXSize = int.Parse(blockXSizeString);
|
||||
int blockYSize = int.Parse(blockYSizeString);
|
||||
AstcDecoder.DecodeASTC(input, width, height, blockXSize, blockYSize, output);
|
||||
AstcDecoder.DecodeASTC<ColorBGRA32, byte>(input, width, height, blockXSize, blockYSize, output);
|
||||
}
|
||||
|
||||
private static void DecodeAtc(ReadOnlySpan<byte> input, int width, int height, string modeString, Span<byte> output)
|
||||
@@ -120,10 +117,10 @@ internal static partial class Program
|
||||
switch (mode)
|
||||
{
|
||||
case 0:
|
||||
AtcDecoder.DecompressAtcRgb4(input, width, height, output);
|
||||
AtcDecoder.DecompressAtcRgb4<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 1:
|
||||
AtcDecoder.DecompressAtcRgba8(input, width, height, output);
|
||||
AtcDecoder.DecompressAtcRgba8<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -146,25 +143,25 @@ internal static partial class Program
|
||||
switch (mode)
|
||||
{
|
||||
case 1:
|
||||
Bc1.Decompress(input, width, height, output);
|
||||
Bc1.Decompress<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 2:
|
||||
Bc2.Decompress(input, width, height, output);
|
||||
Bc2.Decompress<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 3:
|
||||
Bc3.Decompress(input, width, height, output);
|
||||
Bc3.Decompress<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 4:
|
||||
Bc4.Decompress(input, width, height, output);
|
||||
Bc4.Decompress<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 5:
|
||||
Bc5.Decompress(input, width, height, output);
|
||||
Bc5.Decompress<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 6:
|
||||
Bc6h.Decompress(input, width, height, bool.Parse(isSignedString), output);
|
||||
Bc6h.Decompress<ColorBGRA32, byte>(input, width, height, bool.Parse(isSignedString), output);
|
||||
break;
|
||||
case 7:
|
||||
Bc7.Decompress(input, width, height, output);
|
||||
Bc7.Decompress<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -182,13 +179,13 @@ internal static partial class Program
|
||||
switch (mode)
|
||||
{
|
||||
case 0:
|
||||
DxtDecoder.DecompressDXT1(input, width, height, output);
|
||||
DxtDecoder.DecompressDXT1<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 1:
|
||||
DxtDecoder.DecompressDXT3(input, width, height, output);
|
||||
DxtDecoder.DecompressDXT3<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 2:
|
||||
DxtDecoder.DecompressDXT5(input, width, height, output);
|
||||
DxtDecoder.DecompressDXT5<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -211,28 +208,28 @@ internal static partial class Program
|
||||
switch (mode)
|
||||
{
|
||||
case 0:
|
||||
EtcDecoder.DecompressETC(input, width, height, output);
|
||||
EtcDecoder.DecompressETC<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 1:
|
||||
EtcDecoder.DecompressETC2(input, width, height, output);
|
||||
EtcDecoder.DecompressETC2<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 2:
|
||||
EtcDecoder.DecompressETC2A1(input, width, height, output);
|
||||
EtcDecoder.DecompressETC2A1<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 3:
|
||||
EtcDecoder.DecompressETC2A8(input, width, height, output);
|
||||
EtcDecoder.DecompressETC2A8<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 4:
|
||||
EtcDecoder.DecompressEACRUnsigned(input, width, height, output);
|
||||
EtcDecoder.DecompressEACRUnsigned<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 5:
|
||||
EtcDecoder.DecompressEACRSigned(input, width, height, output);
|
||||
EtcDecoder.DecompressEACRSigned<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 6:
|
||||
EtcDecoder.DecompressEACRGUnsigned(input, width, height, output);
|
||||
EtcDecoder.DecompressEACRGUnsigned<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
case 7:
|
||||
EtcDecoder.DecompressEACRGSigned(input, width, height, output);
|
||||
EtcDecoder.DecompressEACRGSigned<ColorBGRA32, byte>(input, width, height, output);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -244,13 +241,6 @@ internal static partial class Program
|
||||
{
|
||||
Console.WriteLine("Arg at index 5 : 2bitMode");
|
||||
bool do2bit = bool.Parse(do2bitModeString);
|
||||
PvrtcDecoder.DecompressPVRTC(input, width, height, do2bit, output);
|
||||
}
|
||||
|
||||
public static void WriteAllBytes(string path, ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(path, nameof(path));
|
||||
using Microsoft.Win32.SafeHandles.SafeFileHandle sfh = File.OpenHandle(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
RandomAccess.Write(sfh, bytes, 0);
|
||||
PvrtcDecoder.DecompressPVRTC<ColorBGRA32, byte>(input, width, height, do2bit, output);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" />
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -5,14 +5,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.1.0">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NUnit" Version="4.4.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.10.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AssetRipper.TextureDecoder.Astc;
|
||||
using AssetRipper.TextureDecoder.Rgb.Formats;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Tests;
|
||||
|
||||
@@ -67,7 +68,7 @@ public sealed class AstcTests
|
||||
private static void AssertCorrectDecompression<T>(int blockWidth, int blockHeight, double maxMeanDeviation, double maxStandardDeviation) where T : ITexture
|
||||
{
|
||||
ReadOnlySpan<byte> data = T.Data;
|
||||
int bytesRead = AstcDecoder.DecodeASTC(data, T.Width, T.Height, blockWidth, blockHeight, out byte[] decompressedData);
|
||||
int bytesRead = AstcDecoder.DecodeASTC<ColorBGRA32, byte>(data, T.Width, T.Height, blockWidth, blockHeight, out byte[] decompressedData);
|
||||
if (!T.Mips)
|
||||
{
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
@@ -78,6 +79,6 @@ public sealed class AstcTests
|
||||
[TestCase(new byte[16] { 83, 1, 147, 104, 198, 173, 55, 116, 182, 66, 105, 11, 102, 43, 148, 125 }, 8, 8, TestName = "GetBits Should not throw in DecodeIntseq")]
|
||||
public void DecodesBlockWithoutThrowing(byte[] block, int blockWidth, int blockHeight)
|
||||
{
|
||||
Assert.DoesNotThrow(() => AstcDecoder.DecodeASTC(block, blockWidth, blockHeight, blockWidth, blockHeight, out _));
|
||||
Assert.DoesNotThrow(() => AstcDecoder.DecodeASTC<ColorBGRA32, byte>(block, blockWidth, blockHeight, blockWidth, blockHeight, out _));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace AssetRipper.TextureDecoder.Tests
|
||||
using AssetRipper.TextureDecoder.Rgb.Formats;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Tests
|
||||
{
|
||||
public sealed class AtcTests
|
||||
{
|
||||
@@ -6,7 +8,7 @@
|
||||
public void DecompressAtcRgb4Test()
|
||||
{
|
||||
byte[] data = File.ReadAllBytes(TestFileFolders.AtcTestFiles + "test.atc_rgb4");
|
||||
int bytesRead = Atc.AtcDecoder.DecompressAtcRgb4(data, 256, 256, out byte[] decodedData);
|
||||
int bytesRead = Atc.AtcDecoder.DecompressAtcRgb4<ColorBGRA32, byte>(data, 256, 256, out byte[] decodedData);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
@@ -18,7 +20,7 @@
|
||||
public void DecompressAtcRgba8Test()
|
||||
{
|
||||
byte[] data = File.ReadAllBytes(TestFileFolders.AtcTestFiles + "test.atc_rgba8");
|
||||
int bytesRead = Atc.AtcDecoder.DecompressAtcRgba8(data, 256, 256, out byte[] decodedData);
|
||||
int bytesRead = Atc.AtcDecoder.DecompressAtcRgba8<ColorBGRA32, byte>(data, 256, 256, out byte[] decodedData);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
|
||||
@@ -21,14 +21,14 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressDXT1Test()
|
||||
public void Decompress_BC1()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1");
|
||||
int totalBytesRead = 0;
|
||||
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4 }) //mipmaps 2 and 1 cause a buffer overrun for the output
|
||||
{
|
||||
int bytesRead = Bc1.Decompress(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
|
||||
DxtDecoder.DecompressDXT1(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
|
||||
int bytesRead = Bc1.Decompress<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
|
||||
DxtDecoder.DecompressDXT1<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
|
||||
totalBytesRead += bytesRead;
|
||||
AssertAlmostEqual(dxtDecodedData, bcDecodedData);
|
||||
}
|
||||
@@ -36,14 +36,14 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressDXT3Test()
|
||||
public void Decompress_BC2()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3");
|
||||
int totalBytesRead = 0;
|
||||
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4 }) //mipmaps 2 and 1 cause a buffer overrun for the output
|
||||
{
|
||||
int bytesRead = Bc2.Decompress(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
|
||||
DxtDecoder.DecompressDXT3(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
|
||||
int bytesRead = Bc2.Decompress<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
|
||||
DxtDecoder.DecompressDXT3<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
|
||||
totalBytesRead += bytesRead;
|
||||
AssertAlmostEqual(dxtDecodedData, bcDecodedData);
|
||||
}
|
||||
@@ -51,14 +51,14 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressDXT5Test()
|
||||
public void Decompress_BC3()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5");
|
||||
int totalBytesRead = 0;
|
||||
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4 }) //mipmaps 2 and 1 cause a buffer overrun for the output
|
||||
{
|
||||
int bytesRead = Bc3.Decompress(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
|
||||
DxtDecoder.DecompressDXT5(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
|
||||
int bytesRead = Bc3.Decompress<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
|
||||
DxtDecoder.DecompressDXT5<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
|
||||
totalBytesRead += bytesRead;
|
||||
AssertAlmostEqual(dxtDecodedData, bcDecodedData);
|
||||
}
|
||||
@@ -66,25 +66,25 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressBC4Test()
|
||||
public void Decompress_BC4()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4");
|
||||
int bytesRead = Bc4.Decompress(data, 512, 512, out _);
|
||||
int bytesRead = Bc4.Decompress<ColorBGRA32, byte>(data, 512, 512, out _);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressBC5Test()
|
||||
public void Decompress_BC5()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5");
|
||||
int bytesRead = Bc5.Decompress(data, 512, 512, out _);
|
||||
int bytesRead = Bc5.Decompress<ColorBGRA32, byte>(data, 512, 512, out _);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
}
|
||||
|
||||
[TestCase("test.bc6h_fast")]
|
||||
[TestCase("test.bc6h_normal")]
|
||||
[TestCase("test.bc6h_best")]
|
||||
public void DecompressBC6HTest(string fileName)
|
||||
public void Decompress_BC6H(string fileName)
|
||||
{
|
||||
AssertCorrectBC6HDecompression(TestFileFolders.BcTestFiles + fileName, 512, 512, false);
|
||||
}
|
||||
@@ -92,19 +92,90 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
[TestCase("test.bc7_fast")]
|
||||
[TestCase("test.bc7_normal")]
|
||||
[TestCase("test.bc7_best")]
|
||||
public void DecompressBC7Test(string fileName)
|
||||
public void Decompress_BC7(string fileName)
|
||||
{
|
||||
AssertCorrectBC7Decompression(TestFileFolders.BcTestFiles + fileName, 512, 512);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("Not implemented")]
|
||||
public void BC7PartialBlockTest([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
public void PartialBlock_BC1([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1").AsSpan()[..Bc1.BlockSize];
|
||||
int bytesRead = Bc1.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC2([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3").AsSpan()[..Bc2.BlockSize];
|
||||
int bytesRead = Bc2.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC3([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5").AsSpan()[..Bc3.BlockSize];
|
||||
int bytesRead = Bc3.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC4([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4").AsSpan()[..Bc4.BlockSize];
|
||||
int bytesRead = Bc4.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC5([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5").AsSpan()[..Bc5.BlockSize];
|
||||
int bytesRead = Bc5.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC6H([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc6h_best").AsSpan()[..Bc6h.BlockSize];
|
||||
int bytesRead = Bc6h.Decompress<ColorBGRA32, byte>(data, width, height, false, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC7([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc7_best").AsSpan()[..Bc7.BlockSize];
|
||||
int bytesRead = Bc7.Decompress(data, width, height, out byte[] decodedData);
|
||||
int bytesRead = Bc7.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
@@ -113,7 +184,7 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
private static void AssertCorrectBC6HDecompression(string path, int width, int height, bool isSigned)
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(path);
|
||||
int bytesRead = Bc6h.Decompress(data, width, height, isSigned, out byte[] decodedData);
|
||||
int bytesRead = Bc6h.Decompress<ColorBGRA32, byte>(data, width, height, isSigned, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
ByteArrayDeviation.AssertMinimalDeviation(decodedData, originalBgra32LogoData, MaxMeanDeviationBc6h, MaxStandardDeviationBc6h);
|
||||
}
|
||||
@@ -121,7 +192,7 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
private static void AssertCorrectBC7Decompression(string path, int width, int height)
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(path);
|
||||
int bytesRead = Bc7.Decompress(data, width, height, out byte[] decodedData);
|
||||
int bytesRead = Bc7.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
ByteArrayDeviation.AssertMinimalDeviation(decodedData, originalBgra32LogoData, MaxMeanDeviationBc7, MaxStandardDeviationBc7);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace AssetRipper.TextureDecoder.Tests
|
||||
using AssetRipper.TextureDecoder.Rgb.Formats;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Tests
|
||||
{
|
||||
public sealed class DxtTests
|
||||
{
|
||||
@@ -9,7 +11,7 @@
|
||||
int totalBytesRead = 0;
|
||||
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
|
||||
{
|
||||
int bytesRead = Dxt.DxtDecoder.DecompressDXT1(data.Slice(totalBytesRead), size, size, out _);
|
||||
int bytesRead = Dxt.DxtDecoder.DecompressDXT1<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out _);
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
Assert.That(totalBytesRead, Is.EqualTo(data.Length));
|
||||
@@ -22,7 +24,7 @@
|
||||
int totalBytesRead = 0;
|
||||
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
|
||||
{
|
||||
int bytesRead = Dxt.DxtDecoder.DecompressDXT3(data.Slice(totalBytesRead), size, size, out _);
|
||||
int bytesRead = Dxt.DxtDecoder.DecompressDXT3<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out _);
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
Assert.That(totalBytesRead, Is.EqualTo(data.Length));
|
||||
@@ -35,7 +37,7 @@
|
||||
int totalBytesRead = 0;
|
||||
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
|
||||
{
|
||||
int bytesRead = Dxt.DxtDecoder.DecompressDXT5(data.Slice(totalBytesRead), size, size, out _);
|
||||
int bytesRead = Dxt.DxtDecoder.DecompressDXT5<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out _);
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
Assert.That(totalBytesRead, Is.EqualTo(data.Length));
|
||||
|
||||
@@ -6,14 +6,14 @@ namespace AssetRipper.TextureDecoder.Tests;
|
||||
|
||||
public sealed class EtcTests
|
||||
{
|
||||
private static DecodingDelegate ETCDelegate { get; } = (ArraySegment<byte> data, int width, int height, out byte[] decompressedData) => EtcDecoder.DecompressETC(data, width, height, out decompressedData);
|
||||
private static DecodingDelegate ETC2Delegate { get; } = (ArraySegment<byte> data, int width, int height, out byte[] decompressedData) => EtcDecoder.DecompressETC2(data, width, height, out decompressedData);
|
||||
private static DecodingDelegate ETC2A1Delegate { get; } = (ArraySegment<byte> data, int width, int height, out byte[] decompressedData) => EtcDecoder.DecompressETC2A1(data, width, height, out decompressedData);
|
||||
private static DecodingDelegate ETC2A8Delegate { get; } = (ArraySegment<byte> data, int width, int height, out byte[] decompressedData) => EtcDecoder.DecompressETC2A8(data, width, height, out decompressedData);
|
||||
private static DecodingDelegate EACRSignedDelegate { get; } = (ArraySegment<byte> data, int width, int height, out byte[] decompressedData) => EtcDecoder.DecompressEACRSigned(data, width, height, out decompressedData);
|
||||
private static DecodingDelegate EACRUnsignedDelegate { get; } = (ArraySegment<byte> data, int width, int height, out byte[] decompressedData) => EtcDecoder.DecompressEACRUnsigned(data, width, height, out decompressedData);
|
||||
private static DecodingDelegate EACRGSignedDelegate { get; } = (ArraySegment<byte> data, int width, int height, out byte[] decompressedData) => EtcDecoder.DecompressEACRGSigned(data, width, height, out decompressedData);
|
||||
private static DecodingDelegate EACRGUnsignedDelegate { get; } = (ArraySegment<byte> data, int width, int height, out byte[] decompressedData) => EtcDecoder.DecompressEACRGUnsigned(data, width, height, out decompressedData);
|
||||
private static DecodingDelegate ETCDelegate { get; } = EtcDecoder.DecompressETC<ColorBGRA32, byte>;
|
||||
private static DecodingDelegate ETC2Delegate { get; } = EtcDecoder.DecompressETC2<ColorBGRA32, byte>;
|
||||
private static DecodingDelegate ETC2A1Delegate { get; } = EtcDecoder.DecompressETC2A1<ColorBGRA32, byte>;
|
||||
private static DecodingDelegate ETC2A8Delegate { get; } = EtcDecoder.DecompressETC2A8<ColorBGRA32, byte>;
|
||||
private static DecodingDelegate EACRSignedDelegate { get; } = EtcDecoder.DecompressEACRSigned<ColorBGRA32, byte>;
|
||||
private static DecodingDelegate EACRUnsignedDelegate { get; } = EtcDecoder.DecompressEACRUnsigned<ColorBGRA32, byte>;
|
||||
private static DecodingDelegate EACRGSignedDelegate { get; } = EtcDecoder.DecompressEACRGSigned<ColorBGRA32, byte>;
|
||||
private static DecodingDelegate EACRGUnsignedDelegate { get; } = EtcDecoder.DecompressEACRGUnsigned<ColorBGRA32, byte>;
|
||||
|
||||
[Test]
|
||||
public void DecompressETCTest() => AssertCorrectByteCountReadFor256SquareWithMips(TestFileFolders.EtcTestFiles + "test.etc", ETCDelegate);
|
||||
@@ -57,7 +57,7 @@ public sealed class EtcTests
|
||||
[Test]
|
||||
public void CorrectnessEACRGUnsignedTest() => AssertCorrectDecompression<AndroidTextures.Logo_15, ColorRG<byte>>(EACRGUnsignedDelegate, .1, .6);
|
||||
|
||||
private delegate int DecodingDelegate(ArraySegment<byte> data, int width, int height, out byte[] decompressedData);
|
||||
private delegate int DecodingDelegate(ReadOnlySpan<byte> data, int width, int height, out byte[] decompressedData);
|
||||
|
||||
private static void AssertCorrectByteCountReadFor256SquareWithMips(string path, DecodingDelegate decoder)
|
||||
{
|
||||
@@ -65,7 +65,7 @@ public sealed class EtcTests
|
||||
int totalBytesRead = 0;
|
||||
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
|
||||
{
|
||||
int bytesRead = decoder.Invoke(new ArraySegment<byte>(data, totalBytesRead, data.Length - totalBytesRead), size, size, out _);
|
||||
int bytesRead = decoder.Invoke(new ReadOnlySpan<byte>(data, totalBytesRead, data.Length - totalBytesRead), size, size, out _);
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
Assert.That(totalBytesRead, Is.EqualTo(data.Length));
|
||||
@@ -79,7 +79,7 @@ public sealed class EtcTests
|
||||
private static void AssertCorrectDecompression<TTexture, TColor>(DecodingDelegate decoder, double maxMeanDeviation, double maxStandardDeviation) where TTexture : ITexture where TColor : unmanaged, IColor<TColor, byte>
|
||||
{
|
||||
byte[] data = TTexture.Data;
|
||||
int bytesRead = decoder.Invoke(new ArraySegment<byte>(data), TTexture.Width, TTexture.Height, out byte[] decompressedData);
|
||||
int bytesRead = decoder.Invoke(new ReadOnlySpan<byte>(data), TTexture.Width, TTexture.Height, out byte[] decompressedData);
|
||||
if (!TTexture.Mips)
|
||||
{
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AssetRipper.TextureDecoder.Pvrtc;
|
||||
using AssetRipper.TextureDecoder.Rgb.Formats;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Tests;
|
||||
|
||||
@@ -11,7 +12,7 @@ public sealed class PvrtcTests
|
||||
int totalBytesRead = 0;
|
||||
foreach (int size in new int[] { 512, 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
|
||||
{
|
||||
int bytesRead = PvrtcDecoder.DecompressPVRTC(data, size, size, false, out _);
|
||||
int bytesRead = PvrtcDecoder.DecompressPVRTC<ColorBGRA32, byte>(data, size, size, false, out _);
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
Assert.That(totalBytesRead, Is.EqualTo(data.Length));
|
||||
@@ -32,7 +33,7 @@ public sealed class PvrtcTests
|
||||
private static void AssertCorrectDecompression<T>(bool do2bitMode, double maxMeanDeviation, double maxStandardDeviation) where T : ITexture
|
||||
{
|
||||
ReadOnlySpan<byte> data = T.Data;
|
||||
int bytesRead = PvrtcDecoder.DecompressPVRTC(data, T.Width, T.Height, do2bitMode, out byte[] decompressedData);
|
||||
int bytesRead = PvrtcDecoder.DecompressPVRTC<ColorBGRA32, byte>(data, T.Width, T.Height, do2bitMode, out byte[] decompressedData);
|
||||
if (!T.Mips)
|
||||
{
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using AssetRipper.TextureDecoder.Rgb;
|
||||
using AssetRipper.TextureDecoder.Rgb.Formats;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Astc
|
||||
@@ -12,51 +14,58 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
}
|
||||
}
|
||||
|
||||
public static int DecodeASTC(ReadOnlySpan<byte> input, int width, int height, int blockWidth, int blockHeight, out byte[] output)
|
||||
public static int DecodeASTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, int blockWidth, int blockHeight, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecodeASTC(input, width, height, blockWidth, blockHeight, output);
|
||||
return DecodeASTC<TOutputColor, TOutputChannelValue>(input, width, height, blockWidth, blockHeight, output);
|
||||
}
|
||||
|
||||
public static int DecodeASTC(ReadOnlySpan<byte> input, int width, int height, int blockWidth, int blockHeight, Span<byte> output)
|
||||
public static int DecodeASTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, int blockWidth, int blockHeight, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecodeASTC<TOutputColor, TOutputChannelValue>(input, width, height, blockWidth, blockHeight, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecodeASTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, int blockWidth, int blockHeight, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
ValidateBlockDimension(blockWidth);
|
||||
ValidateBlockDimension(blockHeight);
|
||||
int bcw = (width + blockWidth - 1) / blockWidth;
|
||||
int bch = (height + blockHeight - 1) / blockHeight;
|
||||
int clen_last = (width + blockWidth - 1) % blockWidth + 1;
|
||||
Span<uint> buf = stackalloc uint[blockWidth * blockHeight];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[blockWidth * blockHeight];
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 16)
|
||||
{
|
||||
DecodeBlock(input[inputOffset..], blockWidth, blockHeight, buf);
|
||||
DecodeBlock(input[inputOffset..], blockWidth, blockHeight, buffer);
|
||||
int clen = s < bcw - 1 ? blockWidth : clen_last;
|
||||
for (int i = 0, y = t * blockHeight; i < blockHeight && y < height; i++, y++)
|
||||
{
|
||||
int outputOffsetUInt32 = t * blockHeight * width + s * blockWidth + i * width;
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
WriteUInt32(output, outputOffsetUInt32 + j, buf[j + i * blockWidth]);
|
||||
output[outputOffsetUInt32 + j] = buffer[j + i * blockWidth].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inputOffset;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
static void WriteUInt32(Span<byte> buffer, int offsetUInt32, uint value)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offsetUInt32 * sizeof(uint)), value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeBlock(ReadOnlySpan<byte> input, int blockWidth, int blockHeight, Span<uint> output)
|
||||
private static void DecodeBlock(ReadOnlySpan<byte> input, int blockWidth, int blockHeight, Span<ColorRGBA<byte>> output)
|
||||
{
|
||||
if (input[0] == 0xfc && (input[1] & 1) == 1)
|
||||
{
|
||||
uint c = Color(input[9], input[11], input[13], input[15]);
|
||||
ColorRGBA<byte> c = new(input[9], input[11], input[13], input[15]);
|
||||
for (int i = 0; i < blockWidth * blockHeight; i++)
|
||||
{
|
||||
output[i] = c;
|
||||
@@ -674,7 +683,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplicateColor(BlockData block, Span<uint> output)
|
||||
private static void ApplicateColor(BlockData block, Span<ColorRGBA<byte>> output)
|
||||
{
|
||||
if (block.dual_plane != 0)
|
||||
{
|
||||
@@ -689,7 +698,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
byte g = SelectColor(block.endpoints[p][1], block.endpoints[p][5], block.weights[i * 2 + ps[1]]);
|
||||
byte b = SelectColor(block.endpoints[p][2], block.endpoints[p][6], block.weights[i * 2 + ps[2]]);
|
||||
byte a = SelectColor(block.endpoints[p][3], block.endpoints[p][7], block.weights[i * 2 + ps[3]]);
|
||||
output[i] = Color(r, g, b, a);
|
||||
output[i] = new(r, g, b, a);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -700,7 +709,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
byte g = SelectColor(block.endpoints[0][1], block.endpoints[0][5], block.weights[i * 2 + ps[1]]);
|
||||
byte b = SelectColor(block.endpoints[0][2], block.endpoints[0][6], block.weights[i * 2 + ps[2]]);
|
||||
byte a = SelectColor(block.endpoints[0][3], block.endpoints[0][7], block.weights[i * 2 + ps[3]]);
|
||||
output[i] = Color(r, g, b, a);
|
||||
output[i] = new(r, g, b, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -713,7 +722,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
byte g = SelectColor(block.endpoints[p][1], block.endpoints[p][5], block.weights[i * 2]);
|
||||
byte b = SelectColor(block.endpoints[p][2], block.endpoints[p][6], block.weights[i * 2]);
|
||||
byte a = SelectColor(block.endpoints[p][3], block.endpoints[p][7], block.weights[i * 2]);
|
||||
output[i] = Color(r, g, b, a);
|
||||
output[i] = new(r, g, b, a);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -724,7 +733,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
byte g = SelectColor(block.endpoints[0][1], block.endpoints[0][5], block.weights[i * 2]);
|
||||
byte b = SelectColor(block.endpoints[0][2], block.endpoints[0][6], block.weights[i * 2]);
|
||||
byte a = SelectColor(block.endpoints[0][3], block.endpoints[0][7], block.weights[i * 2]);
|
||||
output[i] = Color(r, g, b, a);
|
||||
output[i] = new(r, g, b, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -849,12 +858,6 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static uint Color(uint r, uint g, uint b, uint a)
|
||||
{
|
||||
return r << 16 | g << 8 | b | a << 24;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static byte BitReverseU8(byte c, int bits)
|
||||
{
|
||||
@@ -883,7 +886,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static int GetBits(ReadOnlySpan<byte> input, int bit, int len)
|
||||
{
|
||||
ReadOnlySpan<byte> slice = input[(bit / 8)..];
|
||||
ReadOnlySpan<byte> slice = input[(bit / 8)..];
|
||||
int bitBuffer;
|
||||
if (slice.Length >= sizeof(int))
|
||||
{
|
||||
@@ -894,7 +897,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
Span<byte> temp = stackalloc byte[sizeof(int)];
|
||||
slice.CopyTo(temp);
|
||||
bitBuffer = BinaryPrimitives.ReadInt32LittleEndian(temp);
|
||||
}
|
||||
}
|
||||
return (bitBuffer >> (bit % 8)) & ((1 << len) - 1);
|
||||
}
|
||||
|
||||
@@ -996,10 +999,12 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static byte SelectColor(int v0, int v1, int weight)
|
||||
{
|
||||
return (byte)(((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * 255 + 32768) / 65536);
|
||||
const int SignAdjustment = short.MaxValue + 1;
|
||||
const int MaxValue = ushort.MaxValue + 1;
|
||||
return (byte)(((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * byte.MaxValue + SignAdjustment) / MaxValue);
|
||||
}
|
||||
|
||||
private static readonly byte[] BitReverseTable =
|
||||
private static ReadOnlySpan<byte> BitReverseTable =>
|
||||
[
|
||||
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
|
||||
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
|
||||
@@ -1019,15 +1024,15 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
|
||||
];
|
||||
|
||||
private static readonly int[] WeightPrecTableA = [0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0];
|
||||
private static readonly int[] WeightPrecTableB = [0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5];
|
||||
private static ReadOnlySpan<int> WeightPrecTableA => [0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0];
|
||||
private static ReadOnlySpan<int> WeightPrecTableB => [0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5];
|
||||
|
||||
private static readonly int[] CemTableA = [0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0];
|
||||
private static readonly int[] CemTableB = [8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1];
|
||||
private static ReadOnlySpan<int> CemTableA => [0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0];
|
||||
private static ReadOnlySpan<int> CemTableB => [8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1];
|
||||
|
||||
private static readonly int[] DImt = [0, 2, 4, 5, 7];
|
||||
private static readonly int[] DImq = [0, 3, 5];
|
||||
private static readonly int[] DITritsTable =
|
||||
private static ReadOnlySpan<int> DImt => [0, 2, 4, 5, 7];
|
||||
private static ReadOnlySpan<int> DImq => [0, 3, 5];
|
||||
private static ReadOnlySpan<int> DITritsTable =>
|
||||
[
|
||||
0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2,
|
||||
0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1,
|
||||
@@ -1035,14 +1040,14 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
|
||||
];
|
||||
private static readonly int[] DIQuintsTable =
|
||||
private static ReadOnlySpan<int> DIQuintsTable =>
|
||||
[
|
||||
0, 1, 2, 3, 4, 0, 4, 4, 0, 1, 2, 3, 4, 1, 4, 4, 0, 1, 2, 3, 4, 2, 4, 4, 0, 1, 2, 3, 4, 3, 4, 4, 0, 1, 2, 3, 4, 0, 4, 0, 0, 1, 2, 3, 4, 1, 4, 1, 0, 1, 2, 3, 4, 2, 4, 2, 0, 1, 2, 3, 4, 3, 4, 3, 0, 1, 2, 3, 4, 0, 2, 3, 0, 1, 2, 3, 4, 1, 2, 3, 0, 1, 2, 3, 4, 2, 2, 3, 0, 1, 2, 3, 4, 3, 2, 3, 0, 1, 2, 3, 4, 0, 0, 1, 0, 1, 2, 3, 4, 1, 0, 1, 0, 1, 2, 3, 4, 2, 0, 1, 0, 1, 2, 3, 4, 3, 0, 1,
|
||||
0, 0, 0, 0, 0, 4, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 0, 0, 0, 0, 0, 4, 0, 4, 1, 1, 1, 1, 1, 4, 1, 4, 2, 2, 2, 2, 2, 4, 2, 4, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3,
|
||||
0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 3, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4,
|
||||
];
|
||||
|
||||
private static readonly int[] DETritsTable = [0, 204, 93, 44, 22, 11, 5];
|
||||
private static readonly int[] DEQuintsTable = [0, 113, 54, 26, 13, 6];
|
||||
private static ReadOnlySpan<int> DETritsTable => [0, 204, 93, 44, 22, 11, 5];
|
||||
private static ReadOnlySpan<int> DEQuintsTable => [0, 113, 54, 26, 13, 6];
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,48 @@
|
||||
using AssetRipper.TextureDecoder.Rgb;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Atc
|
||||
{
|
||||
public static class AtcDecoder
|
||||
{
|
||||
public static int DecompressAtcRgb4(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressAtcRgb4<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressAtcRgb4(input, width, height, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return DecompressAtcRgb4<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressAtcRgb4(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressAtcRgb4<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressAtcRgb4<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressAtcRgb4<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<TOutputColor> buf = stackalloc TOutputColor[16];
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 8)
|
||||
{
|
||||
DecodeAtcRgb4Block(input.Slice(inputOffset, 8), buf);
|
||||
DecodeAtcRgb4Block<TOutputColor, TOutputChannelValue>(input.Slice(inputOffset, 8), buf);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
int outputOffset = t * 16 * width + s * 16 + i * width * sizeof(uint);
|
||||
int outputOffset = t * 4 * width + s * 4 + i * width;
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(output.Slice(outputOffset + j * sizeof(uint)), buf[j + 4 * i]);
|
||||
output[outputOffset + j] = buf[j + 4 * i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,31 +50,44 @@ namespace AssetRipper.TextureDecoder.Atc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
public static int DecompressAtcRgba8(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressAtcRgba8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressAtcRgba8(input, width, height, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return DecompressAtcRgba8<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressAtcRgba8(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressAtcRgba8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressAtcRgba8<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressAtcRgba8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<TOutputColor> buf = stackalloc TOutputColor[16];
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 16)
|
||||
{
|
||||
DecodeAtcRgba8Block(input.Slice(inputOffset, 16), buf);
|
||||
DecodeAtcRgba8Block<TOutputColor, TOutputChannelValue>(input.Slice(inputOffset, 16), buf);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
int outputOffset = t * 16 * width + s * 16 + i * width * sizeof(uint);
|
||||
int outputOffset = t * 4 * width + s * 4 + i * width;
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(output.Slice(outputOffset + j * sizeof(uint)), buf[j + 4 * i]);
|
||||
output[outputOffset + j] = buf[j + 4 * i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +95,9 @@ namespace AssetRipper.TextureDecoder.Atc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
private static void DecodeAtcRgb4Block(ReadOnlySpan<byte> input, Span<uint> output)
|
||||
private static void DecodeAtcRgb4Block<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
Span<int> colors = stackalloc int[16];
|
||||
int c0 = input.ReadAtOffset<ushort>(0);
|
||||
@@ -83,12 +112,14 @@ namespace AssetRipper.TextureDecoder.Atc
|
||||
int cb = colors[cidx * 4 + 0];
|
||||
int cg = colors[cidx * 4 + 1];
|
||||
int cr = colors[cidx * 4 + 2];
|
||||
output[i] = Color(cr, cg, cb, 255);
|
||||
output[i].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>((byte)cr, (byte)cg, (byte)cb, byte.MaxValue);
|
||||
cindex >>= 2;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeAtcRgba8Block(ReadOnlySpan<byte> input, Span<uint> output)
|
||||
private static void DecodeAtcRgba8Block<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
Span<int> alphas = stackalloc int[16];
|
||||
ulong avalue = BinaryPrimitives.ReadUInt64LittleEndian(input);
|
||||
@@ -112,7 +143,7 @@ namespace AssetRipper.TextureDecoder.Atc
|
||||
int cr = colors[cidx * 4 + 2];
|
||||
int aidx = unchecked((int)aindex & 7);
|
||||
int ca = alphas[aidx];
|
||||
output[i] = Color(cr, cg, cb, ca);
|
||||
output[i].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>((byte)cr, (byte)cg, (byte)cb, (byte)ca);
|
||||
cindex >>= 2;
|
||||
aindex >>= 3;
|
||||
}
|
||||
@@ -121,6 +152,7 @@ namespace AssetRipper.TextureDecoder.Atc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static T ReadAtOffset<T>(this ReadOnlySpan<byte> input, int offset) where T : unmanaged
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return MemoryMarshal.Read<T>(input.Slice(offset));
|
||||
}
|
||||
|
||||
@@ -159,9 +191,9 @@ namespace AssetRipper.TextureDecoder.Atc
|
||||
colors[13] = Extend((c1 >> 5) & 0x3F, 6, 8);
|
||||
colors[14] = Extend((c1 >> 11) & 0x1F, 5, 8);
|
||||
|
||||
colors[4] = Math.Max(0, colors[8] - colors[12] / 4);
|
||||
colors[5] = Math.Max(0, colors[9] - colors[13] / 4);
|
||||
colors[6] = Math.Max(0, colors[10] - colors[14] / 4);
|
||||
colors[4] = int.Max(0, colors[8] - colors[12] / 4);
|
||||
colors[5] = int.Max(0, colors[9] - colors[13] / 4);
|
||||
colors[6] = int.Max(0, colors[10] - colors[14] / 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,11 +228,5 @@ namespace AssetRipper.TextureDecoder.Atc
|
||||
// bit-pattern replicating scaling (can at most double the bits)
|
||||
return (value << (to - from)) | (value >> (from * 2 - to));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static uint Color(int r, int g, int b, int a)
|
||||
{
|
||||
return unchecked((uint)(r << 16 | g << 8 | b | a << 24));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,26 +9,56 @@ public static class Bc1
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 8;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
|
||||
return Decompress(input, width, height, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), output.Slice(outputOffset), width * 4);
|
||||
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), buffer);
|
||||
BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,26 +9,56 @@ public static class Bc2
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 16;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
|
||||
return Decompress(input, width, height, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
BcHelpers.DecompressBc2(input.Slice(inputOffset), output.Slice(outputOffset), width * 4);
|
||||
BcHelpers.DecompressBc2(input.Slice(inputOffset, BlockSize), buffer);
|
||||
BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,26 +9,56 @@ public static class Bc3
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 16;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
|
||||
return Decompress(input, width, height, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
BcHelpers.DecompressBc3(input.Slice(inputOffset), output.Slice(outputOffset), width * 4);
|
||||
BcHelpers.DecompressBc3(input.Slice(inputOffset, BlockSize), buffer);
|
||||
BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,30 +10,56 @@ public static class Bc4
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 8;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorR<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
|
||||
return Decompress(input, width, height, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
int bufferSize = width * height * Unsafe.SizeOf<ColorR<byte>>();
|
||||
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int bufferOffset = ((i * width) + j) * Unsafe.SizeOf<ColorR<byte>>();
|
||||
BcHelpers.DecompressBc4(input.Slice(inputOffset), buffer.Slice(bufferOffset), width);
|
||||
BcHelpers.DecompressBc4(input.Slice(inputOffset, BlockSize), buffer);
|
||||
BcHelpers.CopyBufferToOutput<ColorR<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorR<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(bufferArray);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,30 +10,56 @@ public static class Bc5
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 16;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRG<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
|
||||
return Decompress(input, width, height, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
int bufferSize = width * height * Unsafe.SizeOf<ColorRG<byte>>();
|
||||
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int bufferOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRG<byte>>();
|
||||
BcHelpers.DecompressBc5(input.Slice(inputOffset), buffer.Slice(bufferOffset), width * 2);
|
||||
BcHelpers.DecompressBc5(input.Slice(inputOffset, BlockSize), buffer);
|
||||
BcHelpers.CopyBufferToOutput<ColorRG<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRG<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(bufferArray);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,34 +10,56 @@ public static class Bc6h
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 16;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGB<Half>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, bool isSigned, out byte[] output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, bool isSigned, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
|
||||
return Decompress(input, width, height, isSigned, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, isSigned, output);
|
||||
}
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, bool isSigned, Span<byte> output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, bool isSigned, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
int bufferSize = width * height * Unsafe.SizeOf<ColorRGB<Half>>();
|
||||
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, isSigned, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, bool isSigned, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGB<Half>>();
|
||||
BcHelpers.DecompressBc6h(
|
||||
input.Slice(inputOffset, BlockSize),
|
||||
buffer.Slice(outputOffset),
|
||||
width * 3,
|
||||
isSigned);
|
||||
BcHelpers.DecompressBc6h(input.Slice(inputOffset, BlockSize), buffer, isSigned);
|
||||
BcHelpers.CopyBufferToOutput<ColorRGB<Half>, Half, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGB<Half>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorRGB<Half>, Half, ColorBGRA32, byte>(buffer, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(bufferArray);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ internal static class Bc6hTables
|
||||
]
|
||||
];
|
||||
|
||||
internal static int[] AWeight3 { get; } = [0, 9, 18, 27, 37, 46, 55, 64];
|
||||
internal static ReadOnlySpan<int> AWeight3 => [0, 9, 18, 27, 37, 46, 55, 64];
|
||||
|
||||
internal static int[] AWeight4 { get; } = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
|
||||
internal static ReadOnlySpan<int> AWeight4 => [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
|
||||
}
|
||||
@@ -21,29 +21,44 @@ public static class Bc7
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
|
||||
return Decompress(input, width, height, output);
|
||||
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * PixelSize;
|
||||
BcHelpers.DecompressBc7(
|
||||
input.Slice(inputOffset, BlockSize),
|
||||
output.Slice(outputOffset),
|
||||
width * 4);
|
||||
BcHelpers.DecompressBc7(input.Slice(inputOffset, BlockSize), buffer);
|
||||
BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -789,11 +789,11 @@ internal static class Bc7Tables
|
||||
]
|
||||
];
|
||||
|
||||
internal static int[] AWeight2 { get; } = [0, 21, 43, 64];
|
||||
internal static ReadOnlySpan<int> AWeight2 => [0, 21, 43, 64];
|
||||
|
||||
internal static int[] AWeight3 { get; } = [0, 9, 18, 27, 37, 46, 55, 64];
|
||||
internal static ReadOnlySpan<int> AWeight3 => [0, 9, 18, 27, 37, 46, 55, 64];
|
||||
|
||||
internal static int[] AWeight4 { get; } = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
|
||||
internal static ReadOnlySpan<int> AWeight4 => [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
|
||||
|
||||
internal const byte bcdec_bc7_sModeHasPBits = 0b11001011;
|
||||
}
|
||||
@@ -1,47 +1,57 @@
|
||||
using System.Buffers.Binary;
|
||||
using AssetRipper.TextureDecoder.Rgb;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Bc;
|
||||
|
||||
internal static class BcHelpers
|
||||
{
|
||||
public static void DecompressBc1(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc1(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc1.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 4;
|
||||
ColorBlock(compressedBlock, decompressedBlock, destinationPitch, 0);
|
||||
}
|
||||
|
||||
public static void DecompressBc2(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc2(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc2.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 4;
|
||||
ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
|
||||
SharpAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch);
|
||||
}
|
||||
|
||||
public static void DecompressBc3(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc3(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc3.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 4;
|
||||
ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
|
||||
SmoothAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch, 4);
|
||||
}
|
||||
|
||||
public static void DecompressBc4(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc4(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc4.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 1;
|
||||
SmoothAlphaBlock(compressedBlock, decompressedBlock, destinationPitch, 1);
|
||||
}
|
||||
|
||||
public static void DecompressBc5(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc5(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc5.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 2;
|
||||
SmoothAlphaBlock(compressedBlock, decompressedBlock, destinationPitch, 2);
|
||||
SmoothAlphaBlock(compressedBlock.Slice(8), decompressedBlock.Slice(1), destinationPitch, 2);
|
||||
}
|
||||
|
||||
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch, bool isSigned)
|
||||
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, bool isSigned)
|
||||
{
|
||||
BitStream bstream = new()
|
||||
{
|
||||
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock),
|
||||
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong)))
|
||||
};
|
||||
Span<int> r = stackalloc int[4]; // wxyz
|
||||
Span<int> g = stackalloc int[4];
|
||||
Span<int> b = stackalloc int[4];
|
||||
Debug.Assert(decompressedBlock.Length == Bc6h.DecodedBlockSize);
|
||||
|
||||
BitStream bstream = new(compressedBlock);
|
||||
Span<uint> r = stackalloc uint[4]; // wxyz
|
||||
Span<uint> g = stackalloc uint[4];
|
||||
Span<uint> b = stackalloc uint[4];
|
||||
|
||||
int decompressedOffset = 0;
|
||||
|
||||
@@ -52,7 +62,7 @@ internal static class BcHelpers
|
||||
int mode;
|
||||
|
||||
//modes >= 11 (10 in my code) are using 0 one, others will read it from the bitstream
|
||||
int partition;
|
||||
uint partition;
|
||||
|
||||
switch (ReadBc6hModeBits(ref bstream))
|
||||
{
|
||||
@@ -452,15 +462,7 @@ internal static class BcHelpers
|
||||
// Do not use these in your encoder. If the hardware is passed blocks
|
||||
// with one of these modes specified, the resulting decompressed block
|
||||
// must contain all zeroes in all channels except for the alpha channel.
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
Span<byte> pixel = decompressedBlock.Slice(decompressedOffset + (j * 3 * sizeof(ushort)), 3 * sizeof(ushort));
|
||||
pixel.Clear();
|
||||
}
|
||||
decompressedOffset += destinationPitch * sizeof(ushort);
|
||||
}
|
||||
decompressedBlock.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -477,7 +479,7 @@ internal static class BcHelpers
|
||||
|
||||
// Mode 11 (like Mode 10) does not use delta compression,
|
||||
// and instead stores both color endpoints explicitly.
|
||||
if ((mode != 9 && mode != 10) || isSigned)
|
||||
if ((mode is not 9 and not 10) || isSigned)
|
||||
{
|
||||
for (int i = 1; i < (numPartitions + 1) * 2; ++i)
|
||||
{
|
||||
@@ -487,7 +489,7 @@ internal static class BcHelpers
|
||||
}
|
||||
}
|
||||
|
||||
if (mode != 9 && mode != 10)
|
||||
if (mode is not 9 and not 10)
|
||||
{
|
||||
for (int i = 1; i < (numPartitions + 1) * 2; ++i)
|
||||
{
|
||||
@@ -520,7 +522,7 @@ internal static class BcHelpers
|
||||
}
|
||||
partitionSet &= 0x01;
|
||||
|
||||
int index = bstream.ReadBits(indexBits);
|
||||
int index = bstream.ReadBits(indexBits).ToSigned();
|
||||
|
||||
int ep_i = (partitionSet * 2);
|
||||
|
||||
@@ -539,15 +541,15 @@ internal static class BcHelpers
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(decompressedBlock.Slice(bOffset), bFinal);
|
||||
}
|
||||
|
||||
decompressedOffset += destinationPitch * sizeof(ushort);
|
||||
decompressedOffset += 4 * 3 * sizeof(ushort);
|
||||
}
|
||||
|
||||
static int ReadBc6hModeBits(ref BitStream bstream)
|
||||
static uint ReadBc6hModeBits(ref BitStream bstream)
|
||||
{
|
||||
int twoBits = bstream.ReadBits(2);
|
||||
uint twoBits = bstream.ReadBits(2);
|
||||
if (twoBits > 1)
|
||||
{
|
||||
int threeBits = bstream.ReadBits(3);
|
||||
uint threeBits = bstream.ReadBits(3);
|
||||
return (threeBits << 2) | twoBits;
|
||||
}
|
||||
else
|
||||
@@ -557,61 +559,54 @@ internal static class BcHelpers
|
||||
}
|
||||
}
|
||||
|
||||
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
BitStream bstream = new()
|
||||
{
|
||||
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock),
|
||||
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong)))
|
||||
};
|
||||
Span2D<int> endpoints = new(stackalloc int[6 * 4], 6, 4);
|
||||
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
|
||||
Debug.Assert(decompressedBlock.Length == Bc7.DecodedBlockSize);
|
||||
|
||||
int decompressedOffset = 0;
|
||||
BitStream bstream = new(compressedBlock);
|
||||
Span2D<uint> endpoints = new(stackalloc uint[6 * 4], 6, 4);
|
||||
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
|
||||
|
||||
int mode = ReadBc7Mode(ref bstream);
|
||||
|
||||
// unexpected mode, clear the block (transparent black)
|
||||
if (mode >= 8)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 0] = 0;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 1] = 0;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 2] = 0;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 3] = 0;
|
||||
}
|
||||
decompressedOffset += destinationPitch;
|
||||
}
|
||||
decompressedBlock.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int partition = 0;
|
||||
uint partition = 0;
|
||||
int numPartitions = 1;
|
||||
int rotation = 0;
|
||||
int indexSelectionBit = 0;
|
||||
uint rotation = 0;
|
||||
uint indexSelectionBit = 0;
|
||||
|
||||
if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 7)
|
||||
switch (mode)
|
||||
{
|
||||
numPartitions = (mode == 0 || mode == 2) ? 3 : 2;
|
||||
partition = bstream.ReadBits((mode == 0) ? 4 : 6);
|
||||
case 0:
|
||||
numPartitions = 3;
|
||||
partition = bstream.ReadBits(4);
|
||||
break;
|
||||
case 1 or 3 or 7:
|
||||
numPartitions = 2;
|
||||
partition = bstream.ReadBits(6);
|
||||
break;
|
||||
case 2:
|
||||
numPartitions = 3;
|
||||
partition = bstream.ReadBits(6);
|
||||
break;
|
||||
case 4:
|
||||
rotation = bstream.ReadBits(2);
|
||||
indexSelectionBit = bstream.ReadBit();
|
||||
break;
|
||||
case 5:
|
||||
rotation = bstream.ReadBits(2);
|
||||
break;
|
||||
}
|
||||
|
||||
int numEndpoints = numPartitions * 2;
|
||||
|
||||
if (mode == 4 || mode == 5)
|
||||
{
|
||||
rotation = bstream.ReadBits(2);
|
||||
|
||||
if (mode == 4)
|
||||
{
|
||||
indexSelectionBit = bstream.ReadBit();
|
||||
}
|
||||
}
|
||||
|
||||
// Extract endpoints
|
||||
// RGB
|
||||
for (int i = 0; i < 3; ++i)
|
||||
@@ -646,8 +641,8 @@ internal static class BcHelpers
|
||||
// if P-bit is shared
|
||||
if (mode == 1)
|
||||
{
|
||||
int i = bstream.ReadBit();
|
||||
int j = bstream.ReadBit();
|
||||
uint i = bstream.ReadBit();
|
||||
uint j = bstream.ReadBit();
|
||||
|
||||
// rgb component-wise insert pbits
|
||||
for (int k = 0; k < 3; ++k)
|
||||
@@ -663,7 +658,7 @@ internal static class BcHelpers
|
||||
// unique P-bit per endpoint
|
||||
for (int i = 0; i < numEndpoints; ++i)
|
||||
{
|
||||
int j = bstream.ReadBit();
|
||||
uint j = bstream.ReadBit();
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
endpoints[i, k] |= j;
|
||||
@@ -705,10 +700,25 @@ internal static class BcHelpers
|
||||
}
|
||||
|
||||
// Determine weights tables
|
||||
int indexBits = (mode == 0 || mode == 1) ? 3 : ((mode == 6) ? 4 : 2);
|
||||
int indexBits2 = (mode == 4) ? 3 : ((mode == 5) ? 2 : 0);
|
||||
int[] weights = (indexBits == 2) ? Bc7Tables.AWeight2 : ((indexBits == 3) ? Bc7Tables.AWeight3 : Bc7Tables.AWeight4);
|
||||
int[] weights2 = (indexBits2 == 2) ? Bc7Tables.AWeight2 : Bc7Tables.AWeight3;
|
||||
int indexBits = mode switch
|
||||
{
|
||||
0 or 1 => 3,
|
||||
6 => 4,
|
||||
_ => 2
|
||||
};
|
||||
int indexBits2 = mode switch
|
||||
{
|
||||
4 => 3,
|
||||
5 => 2,
|
||||
_ => 0
|
||||
};
|
||||
ReadOnlySpan<int> weights = indexBits switch
|
||||
{
|
||||
2 => Bc7Tables.AWeight2,
|
||||
3 => Bc7Tables.AWeight3,
|
||||
_ => Bc7Tables.AWeight4
|
||||
};
|
||||
ReadOnlySpan<int> weights2 = (indexBits2 == 2) ? Bc7Tables.AWeight2 : Bc7Tables.AWeight3;
|
||||
|
||||
// Quite inconvenient that indices aren't interleaved so we have to make 2 passes here
|
||||
// Pass #1: collecting color indices
|
||||
@@ -726,7 +736,7 @@ internal static class BcHelpers
|
||||
indexBits--;
|
||||
}
|
||||
|
||||
indices[i, j] = bstream.ReadBits(indexBits);
|
||||
indices[i, j] = bstream.ReadBits(indexBits).ToSigned();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,10 +752,10 @@ internal static class BcHelpers
|
||||
|
||||
int index = indices[i, j];
|
||||
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
int a;
|
||||
uint r;
|
||||
uint g;
|
||||
uint b;
|
||||
uint a;
|
||||
|
||||
if (indexBits2 == 0)
|
||||
{
|
||||
@@ -756,7 +766,7 @@ internal static class BcHelpers
|
||||
}
|
||||
else
|
||||
{
|
||||
int index2 = bstream.ReadBits((i | j) != 0 ? indexBits2 : (indexBits2 - 1));
|
||||
int index2 = bstream.ReadBits((i | j) != 0 ? indexBits2 : (indexBits2 - 1)).ToSigned();
|
||||
// The index value for interpolating color comes from the secondary index bits for the texel
|
||||
// if the mode has an index selection bit and its value is one, and from the primary index bits otherwise.
|
||||
// The alpha index comes from the secondary index bits if the block has a secondary index
|
||||
@@ -800,23 +810,23 @@ internal static class BcHelpers
|
||||
break;
|
||||
}
|
||||
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 0] = (byte)r;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 1] = (byte)g;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 2] = (byte)b;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 3] = (byte)a;
|
||||
int offset = (i * 16) + (j * 4);
|
||||
decompressedBlock[offset + 0] = (byte)r;
|
||||
decompressedBlock[offset + 1] = (byte)g;
|
||||
decompressedBlock[offset + 2] = (byte)b;
|
||||
decompressedBlock[offset + 3] = (byte)a;
|
||||
}
|
||||
|
||||
decompressedOffset += destinationPitch;
|
||||
}
|
||||
|
||||
static int ReadBc7Mode(ref BitStream bstream)
|
||||
{
|
||||
int i = 0;
|
||||
while (i < 8 && bstream.ReadBit() == 0)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
// Read bits until we find a non-zero bit, up to 8 bits.
|
||||
// The mode is the number of bits read.
|
||||
uint eightBits = bstream.PeakBits(8);
|
||||
int zeros = BitOperations.TrailingZeroCount(eightBits);
|
||||
int bitCount = zeros >= 8 ? 8 : zeros + 1;
|
||||
bstream.Advance(bitCount);
|
||||
return int.Min(zeros, 8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,6 +959,11 @@ internal static class BcHelpers
|
||||
return (val << (32 - bits)) >> (32 - bits);
|
||||
}
|
||||
|
||||
private static uint ExtendSign(uint val, int bits)
|
||||
{
|
||||
return ExtendSign(val.ToSigned(), bits).ToUnsigned();
|
||||
}
|
||||
|
||||
public static int TransformInverse(int val, int a0, int bits, bool isSigned)
|
||||
{
|
||||
// If the precision of A0 is "p" bits, then the transform algorithm is:
|
||||
@@ -961,6 +976,11 @@ internal static class BcHelpers
|
||||
return val;
|
||||
}
|
||||
|
||||
private static uint TransformInverse(uint val, uint a0, int bits, bool isSigned)
|
||||
{
|
||||
return TransformInverse(val.ToSigned(), a0.ToSigned(), bits, isSigned).ToUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Essentially copy-paste from documentation
|
||||
/// </summary>
|
||||
@@ -1028,11 +1048,21 @@ internal static class BcHelpers
|
||||
return unq;
|
||||
}
|
||||
|
||||
private static uint Unquantize(uint val, int bits, bool isSigned)
|
||||
{
|
||||
return Unquantize(val.ToSigned(), bits, isSigned).ToUnsigned();
|
||||
}
|
||||
|
||||
public static int Interpolate(int a, int b, ReadOnlySpan<int> weights, int index)
|
||||
{
|
||||
return ((a * (64 - weights[index])) + (b * weights[index]) + 32) >> 6;
|
||||
}
|
||||
|
||||
private static uint Interpolate(uint a, uint b, ReadOnlySpan<int> weights, int index)
|
||||
{
|
||||
return Interpolate(a.ToSigned(), b.ToSigned(), weights, index).ToUnsigned();
|
||||
}
|
||||
|
||||
public static ushort FinishUnquantize(int val, bool isSigned)
|
||||
{
|
||||
if (!isSigned)
|
||||
@@ -1041,7 +1071,7 @@ internal static class BcHelpers
|
||||
}
|
||||
else
|
||||
{
|
||||
val = (val < 0) ? -(((-val) * 31) >> 5) : (val * 31) >> 5; // scale the magnitude by 31 / 32
|
||||
val = (val < 0) ? (((-val) * 31) >> 5) : (val * 31) >> 5; // scale the magnitude by 31 / 32
|
||||
int s = 0;
|
||||
if (val < 0)
|
||||
{
|
||||
@@ -1052,8 +1082,35 @@ internal static class BcHelpers
|
||||
}
|
||||
}
|
||||
|
||||
public static void SwapValues(ref int a, ref int b)
|
||||
private static ushort FinishUnquantize(uint val, bool isSigned)
|
||||
{
|
||||
return FinishUnquantize(val.ToSigned(), isSigned);
|
||||
}
|
||||
|
||||
public static void SwapValues(ref uint a, ref uint b)
|
||||
{
|
||||
(a, b) = (b, a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is for copying a decoded block to the output buffer. It handles the pitch and non-4x4 blocks (if necessary).
|
||||
/// </summary>
|
||||
public static void CopyBufferToOutput<TBufferColor, TBufferChannelValue, TOutputColor, TOutputChannelValue>(Span<TBufferColor> buffer, Span<TOutputColor> output, int width, int height, int w, int h, int blockWidth, int blockHeight)
|
||||
where TBufferChannelValue : unmanaged
|
||||
where TBufferColor : unmanaged, IColor<TBufferChannelValue>
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
int hEnd = int.Min(h + blockHeight, height);
|
||||
int wEnd = int.Min(w + blockWidth, width);
|
||||
for (int i = h; i < hEnd; i++)
|
||||
{
|
||||
int outputOffset = i * width;
|
||||
int bufferOffset = (i - h) * blockWidth;
|
||||
for (int j = w; j < wEnd; j++)
|
||||
{
|
||||
output[outputOffset + j] = buffer[bufferOffset + (j - w)].Convert<TBufferColor, TBufferChannelValue, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,59 @@
|
||||
namespace AssetRipper.TextureDecoder.Bc;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
internal struct BitStream
|
||||
namespace AssetRipper.TextureDecoder.Bc;
|
||||
|
||||
internal ref struct BitStream
|
||||
{
|
||||
public ulong low;
|
||||
public ulong high;
|
||||
|
||||
public int ReadBits(int numBits)
|
||||
private UInt128 value;
|
||||
|
||||
private readonly uint BottomBits
|
||||
{
|
||||
uint mask = (uint)((1 << numBits) - 1);
|
||||
// Read the low N bits
|
||||
uint bits = (uint)(this.low & mask);
|
||||
|
||||
this.low >>= numBits;
|
||||
// Put the low N bits of "high" into the high 64-N bits of "low".
|
||||
this.low |= (this.high & mask) << ((sizeof(ulong) * 8) - numBits);
|
||||
this.high >>= numBits;
|
||||
|
||||
return (int)bits;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
get => unchecked((uint)this.value);
|
||||
}
|
||||
|
||||
public int ReadBit()
|
||||
public BitStream(UInt128 value)
|
||||
{
|
||||
return this.ReadBits(1);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public BitStream(ReadOnlySpan<byte> span) : this(BinaryPrimitives.ReadUInt128LittleEndian(span))
|
||||
{
|
||||
}
|
||||
|
||||
public BitStream(ulong low, ulong high)
|
||||
{
|
||||
value = new UInt128(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public readonly uint PeakBits(int numBits)
|
||||
{
|
||||
uint mask = (1u << numBits) - 1u;
|
||||
uint bits = BottomBits & mask;
|
||||
return bits;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void Advance(int numBits)
|
||||
{
|
||||
this.value >>= numBits;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public uint ReadBits(int numBits)
|
||||
{
|
||||
uint bits = PeakBits(numBits);
|
||||
Advance(numBits);
|
||||
return bits;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public uint ReadBit()
|
||||
{
|
||||
uint result = BottomBits & 1u;
|
||||
Advance(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -29,17 +61,11 @@ internal struct BitStream
|
||||
/// </summary>
|
||||
/// <param name="numBits"></param>
|
||||
/// <returns></returns>
|
||||
public int ReadBitsReversed(int numBits)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public uint ReadBitsReversed(int numBits)
|
||||
{
|
||||
int bits = this.ReadBits(numBits);
|
||||
uint bits = this.ReadBits(numBits);
|
||||
// Reverse the bits.
|
||||
int result = 0;
|
||||
while (numBits-- != 0)
|
||||
{
|
||||
result <<= 1;
|
||||
result |= bits & 1;
|
||||
bits >>= 1;
|
||||
}
|
||||
return result;
|
||||
return bits.ReverseBits() >> (32 - numBits);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace AssetRipper.TextureDecoder.Bc;
|
||||
|
||||
internal static class NumericExtensions
|
||||
{
|
||||
public static uint ReverseBits(this uint n)
|
||||
{
|
||||
n = (n >> 1) & 0x55555555 | (n << 1) & 0xAAAAAAAA;
|
||||
n = (n >> 2) & 0x33333333 | (n << 2) & 0xCCCCCCCC;
|
||||
n = (n >> 4) & 0x0F0F0F0F | (n << 4) & 0xF0F0F0F0;
|
||||
n = (n >> 8) & 0x00FF00FF | (n << 8) & 0xFF00FF00;
|
||||
n = (n >> 16) & 0x0000FFFF | (n << 16) & 0xFFFF0000;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static uint ToUnsigned(this int value)
|
||||
{
|
||||
return unchecked((uint)value);
|
||||
}
|
||||
|
||||
public static int ToSigned(this uint value)
|
||||
{
|
||||
return unchecked((int)value);
|
||||
}
|
||||
}
|
||||
@@ -6,32 +6,6 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
{
|
||||
public static class DxtDecoder
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompress a DXT1 image
|
||||
/// </summary>
|
||||
/// <param name="input">Input buffer containing the compressed image.</param>
|
||||
/// <param name="width">Pixel width of the image.</param>
|
||||
/// <param name="height">Pixel height of the image.</param>
|
||||
/// <param name="output">An output buffer of size 4 * width * height.</param>
|
||||
/// <returns>Number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressDXT1(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
return DecompressDXT1<ColorBGRA32, byte>(input, width, height, out output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a DXT1 image
|
||||
/// </summary>
|
||||
/// <param name="input">Input buffer containing the compressed image.</param>
|
||||
/// <param name="width">Pixel width of the image.</param>
|
||||
/// <param name="height">Pixel height of the image.</param>
|
||||
/// <param name="output">An output buffer. Must be at least 4 * width * height.</param>
|
||||
/// <returns>Number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressDXT1(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
return DecompressDXT1<ColorBGRA32, byte>(input, width, height, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a DXT1 image
|
||||
/// </summary>
|
||||
@@ -64,6 +38,7 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressDXT1<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
@@ -127,32 +102,6 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a DXT3 image
|
||||
/// </summary>
|
||||
/// <param name="input">Input buffer containing the compressed image.</param>
|
||||
/// <param name="width">Pixel width of the image.</param>
|
||||
/// <param name="height">Pixel height of the image.</param>
|
||||
/// <param name="output">An output buffer of size 4 * width * height.</param>
|
||||
/// <returns>Number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressDXT3(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
return DecompressDXT3<ColorBGRA32, byte>(input, width, height, out output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a DXT3 image
|
||||
/// </summary>
|
||||
/// <param name="input">Input buffer containing the compressed image.</param>
|
||||
/// <param name="width">Pixel width of the image.</param>
|
||||
/// <param name="height">Pixel height of the image.</param>
|
||||
/// <param name="output">An output buffer. Must be at least 4 * width * height.</param>
|
||||
/// <returns>Number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressDXT3(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
return DecompressDXT3<ColorBGRA32, byte>(input, width, height, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a DXT3 image
|
||||
/// </summary>
|
||||
@@ -185,6 +134,7 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressDXT3<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
@@ -258,32 +208,6 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a DXT5 image
|
||||
/// </summary>
|
||||
/// <param name="input">Input buffer containing the compressed image.</param>
|
||||
/// <param name="width">Pixel width of the image.</param>
|
||||
/// <param name="height">Pixel height of the image.</param>
|
||||
/// <param name="output">An output buffer of size 4 * width * height.</param>
|
||||
/// <returns>Number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressDXT5(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
return DecompressDXT5<ColorBGRA32, byte>(input, width, height, out output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a DXT5 image
|
||||
/// </summary>
|
||||
/// <param name="input">Input buffer containing the compressed image.</param>
|
||||
/// <param name="width">Pixel width of the image.</param>
|
||||
/// <param name="height">Pixel height of the image.</param>
|
||||
/// <param name="output">An output buffer. Must be at least 4 * width * height.</param>
|
||||
/// <returns>Number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressDXT5(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
return DecompressDXT5<ColorBGRA32, byte>(input, width, height, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a DXT5 image
|
||||
/// </summary>
|
||||
@@ -316,6 +240,7 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressDXT5<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
using AssetRipper.TextureDecoder.Rgb;
|
||||
using AssetRipper.TextureDecoder.Rgb.Formats;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Etc
|
||||
{
|
||||
public static class EtcDecoder
|
||||
{
|
||||
public static int DecompressETC(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressETC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressETC(input, width, height, output);
|
||||
return DecompressETC<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressETC(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressETC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressETC<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressETC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -19,22 +35,22 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 8)
|
||||
{
|
||||
DecodeEtc1Block(input.Slice(inputOffset, 8), buf);
|
||||
DecodeEtc1Block(input.Slice(inputOffset, 8), buffer);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
int outputOffset = t * 16 * width + s * 16;
|
||||
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
|
||||
int outputOffset = t * 4 * width + s * 4;
|
||||
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
|
||||
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
outputPtr[j + i * width] = buf[j + 4 * i];
|
||||
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,14 +58,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
public static int DecompressETC2(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressETC2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressETC2(input, width, height, output);
|
||||
return DecompressETC2<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressETC2(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressETC2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressETC2<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressETC2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -59,22 +88,22 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 8)
|
||||
{
|
||||
DecodeEtc2Block(input.Slice(inputOffset, 8), buf);
|
||||
DecodeEtc2Block(input.Slice(inputOffset, 8), buffer);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
int outputOffset = t * 16 * width + s * 16;
|
||||
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
|
||||
int outputOffset = t * 4 * width + s * 4;
|
||||
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
|
||||
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
outputPtr[j + i * width] = buf[j + 4 * i];
|
||||
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,14 +111,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
public static int DecompressETC2A1(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressETC2A1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressETC2A1(input, width, height, output);
|
||||
return DecompressETC2A1<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressETC2A1(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressETC2A1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressETC2A1<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressETC2A1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -99,22 +141,22 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 8)
|
||||
{
|
||||
DecodeEtc2a1Block(input.Slice(inputOffset, 8), buf);
|
||||
DecodeEtc2a1Block(input.Slice(inputOffset, 8), buffer);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
int outputOffset = t * 16 * width + s * 16;
|
||||
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
|
||||
int outputOffset = t * 4 * width + s * 4;
|
||||
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
|
||||
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
outputPtr[j + i * width] = buf[j + 4 * i];
|
||||
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,14 +164,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
public static int DecompressETC2A8(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressETC2A8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressETC2A8(input, width, height, output);
|
||||
return DecompressETC2A8<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressETC2A8(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressETC2A8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressETC2A8<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressETC2A8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -139,23 +194,23 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 16)
|
||||
{
|
||||
DecodeEtc2Block(input.Slice(inputOffset + 8, 8), buf);
|
||||
DecodeEtc2a8Block(input.Slice(inputOffset + 0, 8), buf);
|
||||
DecodeEtc2Block(input.Slice(inputOffset + 8, 8), buffer);
|
||||
DecodeEtc2a8Block(input.Slice(inputOffset + 0, 8), buffer);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
int outputOffset = t * 16 * width + s * 16;
|
||||
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
|
||||
int outputOffset = t * 4 * width + s * 4;
|
||||
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
|
||||
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
outputPtr[j + i * width] = buf[j + 4 * i];
|
||||
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,14 +218,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
public static int DecompressEACRUnsigned(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressEACRUnsigned(input, width, height, output);
|
||||
return DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressEACRUnsigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -180,26 +248,26 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
buf[i] = 0xFF000000;
|
||||
buffer[i] = ColorRGBA<byte>.Black;
|
||||
}
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 8)
|
||||
{
|
||||
DecodeEacUnsignedBlock(input.Slice(inputOffset, 8), buf, 2);
|
||||
DecodeEacUnsignedBlock(input.Slice(inputOffset, 8), buffer, 0);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
int outputOffset = t * 16 * width + s * 16;
|
||||
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
|
||||
int outputOffset = t * 4 * width + s * 4;
|
||||
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
|
||||
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
outputPtr[j + i * width] = buf[j + 4 * i];
|
||||
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,14 +275,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
public static int DecompressEACRSigned(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressEACRSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressEACRSigned(input, width, height, output);
|
||||
return DecompressEACRSigned<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressEACRSigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressEACRSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressEACRSigned<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressEACRSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -224,26 +305,26 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
buf[i] = 0xFF000000;
|
||||
buffer[i] = ColorRGBA<byte>.Black;
|
||||
}
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 8)
|
||||
{
|
||||
DecodeEacSignedBlock(input.Slice(inputOffset, 8), buf, 2);
|
||||
DecodeEacSignedBlock(input.Slice(inputOffset, 8), buffer, 0);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
int outputOffset = t * 16 * width + s * 16;
|
||||
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
|
||||
int outputOffset = t * 4 * width + s * 4;
|
||||
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
|
||||
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
outputPtr[j + i * width] = buf[j + 4 * i];
|
||||
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,14 +332,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
public static int DecompressEACRGUnsigned(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressEACRGUnsigned(input, width, height, output);
|
||||
return DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressEACRGUnsigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
|
||||
@@ -268,27 +362,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
}
|
||||
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
buf[i] = 0xFF000000;
|
||||
buffer[i] = ColorRGBA<byte>.Black;
|
||||
}
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 16)
|
||||
{
|
||||
DecodeEacUnsignedBlock(input.Slice(inputOffset + 0, 8), buf, 2);
|
||||
DecodeEacUnsignedBlock(input.Slice(inputOffset + 8, 8), buf, 1);
|
||||
DecodeEacUnsignedBlock(input.Slice(inputOffset + 0, 8), buffer, 0);
|
||||
DecodeEacUnsignedBlock(input.Slice(inputOffset + 8, 8), buffer, 1);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
int outputOffset = t * 16 * width + s * 16;
|
||||
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
|
||||
|
||||
int outputOffset = t * 4 * width + s * 4;
|
||||
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
|
||||
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
outputPtr[j + i * width] = buf[j + 4 * i];
|
||||
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,14 +390,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
public static int DecompressEACRGSigned(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
public static int DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[width * height * 4];
|
||||
return DecompressEACRGSigned(input, width, height, output);
|
||||
return DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(input, width, height, output);
|
||||
}
|
||||
|
||||
public static int DecompressEACRGSigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
public static int DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
public static int DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -313,27 +420,27 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
int clen_last = (width + 3) % 4 + 1;
|
||||
Span<uint> buf = stackalloc uint[16];
|
||||
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
buf[i] = 0xFF000000;
|
||||
buffer[i] = ColorRGBA<byte>.Black;
|
||||
}
|
||||
int inputOffset = 0;
|
||||
for (int t = 0; t < bch; t++)
|
||||
{
|
||||
for (int s = 0; s < bcw; s++, inputOffset += 16)
|
||||
{
|
||||
DecodeEacSignedBlock(input.Slice(inputOffset + 0, 8), buf, 2);
|
||||
DecodeEacSignedBlock(input.Slice(inputOffset + 8, 8), buf, 1);
|
||||
DecodeEacSignedBlock(input.Slice(inputOffset + 0, 8), buffer, 0);
|
||||
DecodeEacSignedBlock(input.Slice(inputOffset + 8, 8), buffer, 1);
|
||||
int clen = s < bcw - 1 ? 4 : clen_last;
|
||||
int outputOffset = t * 16 * width + s * 16;
|
||||
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
|
||||
int outputOffset = t * 4 * width + s * 4;
|
||||
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
|
||||
|
||||
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
|
||||
{
|
||||
for (int j = 0; j < clen; j++)
|
||||
{
|
||||
outputPtr[j + i * width] = buf[j + 4 * i];
|
||||
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,7 +451,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
/// <summary>
|
||||
/// Parses an 8-byte Etc1 block from <paramref name="input"/>.
|
||||
/// </summary>
|
||||
private static void DecodeEtc1Block(ReadOnlySpan<byte> input, Span<uint> output)
|
||||
private static void DecodeEtc1Block(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
|
||||
{
|
||||
byte i3 = input[3];
|
||||
ReadOnlySpan<int> code =
|
||||
@@ -393,13 +500,12 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int index = k << 1 & 2 | j & 1;
|
||||
int cd = code[s];
|
||||
int m = Etc1ModifierTable[cd + index];
|
||||
uint color = ApplicateColor(c, s, m);
|
||||
int oi = WriteOrderTable[i];
|
||||
output[oi] = color;
|
||||
output[oi] = ApplicateColor(c, s, m);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeEtc2Block(ReadOnlySpan<byte> input, Span<uint> output)
|
||||
private static void DecodeEtc2Block(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
|
||||
{
|
||||
int j = input[6] << 8 | input[7];
|
||||
int k = input[4] << 8 | input[5];
|
||||
@@ -423,7 +529,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
}
|
||||
int ti = input[3] >> 1 & 6 | input[3] & 1;
|
||||
byte d = Etc2DistanceTable[ti];
|
||||
ReadOnlySpan<uint> color_set =
|
||||
ReadOnlySpan<ColorRGBA<byte>> color_set =
|
||||
[
|
||||
ApplicateColorRaw(c, 0),
|
||||
ApplicateColor(c, 1, d),
|
||||
@@ -434,9 +540,8 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
{
|
||||
int index = k << 1 & 2 | j & 1;
|
||||
uint color = color_set[index];
|
||||
int oi = WriteOrderTable[i];
|
||||
output[oi] = color;
|
||||
output[oi] = color_set[index];
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -464,7 +569,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
++di;
|
||||
}
|
||||
byte d = Etc2DistanceTable[di];
|
||||
ReadOnlySpan<uint> color_set =
|
||||
ReadOnlySpan<ColorRGBA<byte>> color_set =
|
||||
[
|
||||
ApplicateColor(c, 0, d),
|
||||
ApplicateColor(c, 0, -d),
|
||||
@@ -474,9 +579,8 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
{
|
||||
int index = k << 1 & 2 | j & 1;
|
||||
uint color = color_set[index];
|
||||
int oi = WriteOrderTable[i];
|
||||
output[oi] = color;
|
||||
output[oi] = color_set[index];
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -507,7 +611,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
byte ri = Clamp255((x * (c[1 * 3 + 0] - c[0 * 3 + 0]) + y * (c[2 * 3 + 0] - c[0 * 3 + 0]) + 4 * c[0 * 3 + 0] + 2) >> 2);
|
||||
byte gi = Clamp255((x * (c[1 * 3 + 1] - c[0 * 3 + 1]) + y * (c[2 * 3 + 1] - c[0 * 3 + 1]) + 4 * c[0 * 3 + 1] + 2) >> 2);
|
||||
byte bi = Clamp255((x * (c[1 * 3 + 2] - c[0 * 3 + 2]) + y * (c[2 * 3 + 2] - c[0 * 3 + 2]) + 4 * c[0 * 3 + 2] + 2) >> 2);
|
||||
output[i] = Color(ri, gi, bi, 255);
|
||||
output[i] = new(ri, gi, bi, byte.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,9 +642,8 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int index = k << 1 & 2 | j & 1;
|
||||
int ci = code[s];
|
||||
int m = Etc1ModifierTable[ci + index];
|
||||
uint color = ApplicateColor(c, s, m);
|
||||
int oi = WriteOrderTable[i];
|
||||
output[oi] = color;
|
||||
output[oi] = ApplicateColor(c, s, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -570,15 +673,14 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
int index = k << 1 & 2 | j & 1;
|
||||
int ci = code[s];
|
||||
int m = Etc1ModifierTable[ci + index];
|
||||
uint color = ApplicateColor(c, s, m);
|
||||
int oi = WriteOrderTable[i];
|
||||
output[oi] = color;
|
||||
output[oi] = ApplicateColor(c, s, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void DecodeEtc2a1Block(ReadOnlySpan<byte> input, Span<uint> output)
|
||||
private static void DecodeEtc2a1Block(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
|
||||
{
|
||||
if ((input[3] & 2) != 0)
|
||||
{
|
||||
@@ -591,7 +693,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeEtc2PunchThrowBlock(ReadOnlySpan<byte> input, Span<uint> output)
|
||||
private static void DecodeEtc2PunchThrowBlock(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
|
||||
{
|
||||
int j = input[6] << 8 | input[7];
|
||||
int k = input[4] << 8 | input[5];
|
||||
@@ -612,7 +714,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
}
|
||||
int ti = input[3] >> 1 & 6 | input[3] & 1;
|
||||
byte d = Etc2DistanceTable[ti];
|
||||
ReadOnlySpan<uint> color_set =
|
||||
ReadOnlySpan<ColorRGBA<byte>> color_set =
|
||||
[
|
||||
ApplicateColorRaw(c, 0),
|
||||
ApplicateColor(c, 1, d),
|
||||
@@ -623,10 +725,8 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
{
|
||||
int index = k << 1 & 2 | j & 1;
|
||||
uint color = color_set[index];
|
||||
uint mask = PunchthroughMaskTable[index];
|
||||
int oi = WriteOrderTable[i];
|
||||
output[oi] = color & mask;
|
||||
output[oi] = PunchthroughBooleanTable[index] ? color_set[index] : default;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -654,7 +754,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
++di;
|
||||
}
|
||||
byte d = Etc2DistanceTable[di];
|
||||
ReadOnlySpan<uint> color_set =
|
||||
ReadOnlySpan<ColorRGBA<byte>> color_set =
|
||||
[
|
||||
ApplicateColor(c, 0, d),
|
||||
ApplicateColor(c, 0, -d),
|
||||
@@ -664,10 +764,8 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
{
|
||||
int index = k << 1 & 2 | j & 1;
|
||||
uint color = color_set[index];
|
||||
uint mask = PunchthroughMaskTable[index];
|
||||
int oi = WriteOrderTable[i];
|
||||
output[oi] = color & mask;
|
||||
output[oi] = PunchthroughBooleanTable[index] ? color_set[index] : default;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -698,7 +796,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
byte ri = Clamp255((x * (c[1 * 3 + 0] - c[0 * 3 + 0]) + y * (c[2 * 3 + 0] - c[0 * 3 + 0]) + 4 * c[0 * 3 + 0] + 2) >> 2);
|
||||
byte gi = Clamp255((x * (c[1 * 3 + 1] - c[0 * 3 + 1]) + y * (c[2 * 3 + 1] - c[0 * 3 + 1]) + 4 * c[0 * 3 + 1] + 2) >> 2);
|
||||
byte bi = Clamp255((x * (c[1 * 3 + 2] - c[0 * 3 + 2]) + y * (c[2 * 3 + 2] - c[0 * 3 + 2]) + 4 * c[0 * 3 + 2] + 2) >> 2);
|
||||
output[i] = Color(ri, gi, bi, 255);
|
||||
output[i] = new(ri, gi, bi, byte.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -725,22 +823,28 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
}
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
{
|
||||
int s = Etc1SubblockTable[ti + i];
|
||||
int index = k << 1 & 2 | j & 1;
|
||||
int ci = code[s];
|
||||
int m = PunchthroughModifierTable[ci + index];
|
||||
uint color = ApplicateColor(c, s, m);
|
||||
uint mask = PunchthroughMaskTable[index];
|
||||
int oi = WriteOrderTable[i];
|
||||
output[oi] = color & mask;
|
||||
if (PunchthroughBooleanTable[index])
|
||||
{
|
||||
int s = Etc1SubblockTable[ti + i];
|
||||
int ci = code[s];
|
||||
int m = PunchthroughModifierTable[ci + index];
|
||||
output[oi] = ApplicateColor(c, s, m);
|
||||
}
|
||||
else
|
||||
{
|
||||
output[oi] = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeEtc2a8Block(ReadOnlySpan<byte> input, Span<uint> output)
|
||||
private static void DecodeEtc2a8Block(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int @base = input[0];
|
||||
int data1 = input[1];
|
||||
int mul = data1 >> 4;
|
||||
@@ -748,48 +852,50 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(3), @base);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(3), @base);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int table = data1 & 0xF;
|
||||
ulong l = Get6SwapedBytes(input);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(3), @base, table, mul, l);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(3), @base, table, mul, l);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeEacUnsignedBlock(ReadOnlySpan<byte> input, Span<uint> output, int channel)
|
||||
private static void DecodeEacUnsignedBlock(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output, int channel)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int @base = input[0];
|
||||
int data1 = input[1];
|
||||
int mul = data1 >> 4;
|
||||
if (mul == 0)
|
||||
{
|
||||
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base);
|
||||
}
|
||||
else
|
||||
{
|
||||
int table = data1 & 0xF;
|
||||
ulong l = Get6SwapedBytes(input);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base, table, mul, l);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base, table, mul, l);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeEacSignedBlock(ReadOnlySpan<byte> input, Span<uint> output, int channel)
|
||||
private static void DecodeEacSignedBlock(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output, int channel)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int @base = 127 + unchecked((sbyte)input[0]);
|
||||
int data1 = input[1];
|
||||
int mul = data1 >> 4;
|
||||
if (mul == 0)
|
||||
{
|
||||
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base);
|
||||
}
|
||||
else
|
||||
{
|
||||
int table = data1 & 0xF;
|
||||
ulong l = Get6SwapedBytes(input);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base, table, mul, l);
|
||||
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base, table, mul, l);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,7 +911,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void DecodeEac11Block(Span<byte> output, int @base, int ti, int mul, ulong l)
|
||||
{
|
||||
ReadOnlySpan<sbyte> table = Etc2AlphaModTable.AsSpan(ti * 8, 8);
|
||||
ReadOnlySpan<sbyte> table = Etc2AlphaModTable.Slice(ti * 8, 8);
|
||||
for (int i = 0; i < 16; i++, l >>= 3)
|
||||
{
|
||||
int val = @base + mul * table[unchecked((int)(l & 0b111))];
|
||||
@@ -813,12 +919,6 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static uint Color(int r, int g, int b, int a)
|
||||
{
|
||||
return unchecked((uint)(r << 16 | g << 8 | b | a << 24));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static byte Clamp255(int n)
|
||||
{
|
||||
@@ -826,21 +926,21 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static uint ApplicateColor(ReadOnlySpan<byte> c, int o, int m)
|
||||
private static ColorRGBA<byte> ApplicateColor(ReadOnlySpan<byte> c, int o, int m)
|
||||
{
|
||||
return Color(Clamp255(c[o * 3 + 0] + m), Clamp255(c[o * 3 + 1] + m), Clamp255(c[o * 3 + 2] + m), 255);
|
||||
return new(Clamp255(c[o * 3 + 0] + m), Clamp255(c[o * 3 + 1] + m), Clamp255(c[o * 3 + 2] + m), byte.MaxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static uint ApplicateColor(ReadOnlySpan<int> c, int o, int m)
|
||||
private static ColorRGBA<byte> ApplicateColor(ReadOnlySpan<int> c, int o, int m)
|
||||
{
|
||||
return Color(Clamp255(c[o * 3 + 0] + m), Clamp255(c[o * 3 + 1] + m), Clamp255(c[o * 3 + 2] + m), 255);
|
||||
return new(Clamp255(c[o * 3 + 0] + m), Clamp255(c[o * 3 + 1] + m), Clamp255(c[o * 3 + 2] + m), byte.MaxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static uint ApplicateColorRaw(ReadOnlySpan<byte> c, int o)
|
||||
private static ColorRGBA<byte> ApplicateColorRaw(ReadOnlySpan<byte> c, int o)
|
||||
{
|
||||
return Color(c[o * 3 + 0], c[o * 3 + 1], c[o * 3 + 2], 255);
|
||||
return new(c[o * 3 + 0], c[o * 3 + 1], c[o * 3 + 2], byte.MaxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -849,14 +949,14 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return data[7] | (uint)data[6] << 8 | (uint)data[5] << 16 | (uint)data[4] << 24 | (ulong)data[3] << 32 | (ulong)data[2] << 40;
|
||||
}
|
||||
|
||||
private static readonly byte[] WriteOrderTable = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
|
||||
private static readonly byte[] WriteOrderTableRev = [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0];
|
||||
private static readonly int[] Etc1SubblockTable =
|
||||
private static ReadOnlySpan<byte> WriteOrderTable => [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
|
||||
private static ReadOnlySpan<byte> WriteOrderTableRev => [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0];
|
||||
private static ReadOnlySpan<int> Etc1SubblockTable =>
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
|
||||
];
|
||||
private static readonly int[] Etc1ModifierTable =
|
||||
private static ReadOnlySpan<int> Etc1ModifierTable =>
|
||||
[
|
||||
2, 8, -2, -8,
|
||||
5, 17, -5, -17,
|
||||
@@ -867,7 +967,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
33, 106, -33, -106,
|
||||
47, 183, -47, -183,
|
||||
];
|
||||
private static readonly int[] PunchthroughModifierTable =
|
||||
private static ReadOnlySpan<int> PunchthroughModifierTable =>
|
||||
[
|
||||
0, 8, 0, -8,
|
||||
0, 17, 0, -17,
|
||||
@@ -878,8 +978,8 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
0, 106, 0, -106,
|
||||
0, 183, 0, -183,
|
||||
];
|
||||
private static readonly byte[] Etc2DistanceTable = [3, 6, 11, 16, 23, 32, 41, 64];
|
||||
private static readonly sbyte[] Etc2AlphaModTable =
|
||||
private static ReadOnlySpan<byte> Etc2DistanceTable => [3, 6, 11, 16, 23, 32, 41, 64];
|
||||
private static ReadOnlySpan<sbyte> Etc2AlphaModTable =>
|
||||
[
|
||||
-3, -6, -9, -15, 2, 5, 8, 14,
|
||||
-3, -7, -10, -13, 2, 6, 9, 12,
|
||||
@@ -898,6 +998,6 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
-4, -6, -8, -9, 3, 5, 7, 8,
|
||||
-3, -5, -7, -9, 2, 4, 6, 8,
|
||||
];
|
||||
private static readonly uint[] PunchthroughMaskTable = [0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF];
|
||||
private static ReadOnlySpan<bool> PunchthroughBooleanTable => [true, true, false, true];
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,65 @@
|
||||
//#define DISABLE_TWINDDLING_ROUTINE
|
||||
#define ASSUME_IMAGE_TILING
|
||||
using AssetRipper.TextureDecoder.Rgb;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
{
|
||||
public static partial class PvrtcDecoder
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompresses PVRTC to RGBA 8888
|
||||
/// Decompresses PVRTC
|
||||
/// </summary>
|
||||
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
|
||||
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
|
||||
/// <param name="input">The PVRTC texture data to decompress</param>
|
||||
/// <param name="xDim">X dimension (width) of the texture</param>
|
||||
/// <param name="yDim">Y dimension (height) of the texture</param>
|
||||
/// <param name="output">The decompressed texture data</param>
|
||||
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
|
||||
/// <returns>The number of bytes read from <paramref name="input"/></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static int DecompressPVRTC(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, out byte[] output)
|
||||
public static int DecompressPVRTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, out byte[] output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
output = new byte[xDim * yDim * sizeof(uint)];
|
||||
return DecompressPVRTC(input, xDim, yDim, do2bitMode, output);
|
||||
output = new byte[xDim * yDim * Unsafe.SizeOf<TOutputColor>()];
|
||||
return DecompressPVRTC<TOutputColor, TOutputChannelValue>(input, xDim, yDim, do2bitMode, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses PVRTC to RGBA 8888
|
||||
/// Decompresses PVRTC
|
||||
/// </summary>
|
||||
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
|
||||
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
|
||||
/// <param name="input">The PVRTC texture data to decompress</param>
|
||||
/// <param name="xDim">X dimension (width) of the texture</param>
|
||||
/// <param name="yDim">Y dimension (height) of the texture</param>
|
||||
/// <param name="output">The decompressed texture data</param>
|
||||
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
|
||||
/// <returns>The number of bytes read from <paramref name="input"/></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static int DecompressPVRTC(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<byte> output)
|
||||
public static int DecompressPVRTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<byte> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressPVRTC<TOutputColor, TOutputChannelValue>(input, xDim, yDim, do2bitMode, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses PVRTC
|
||||
/// </summary>
|
||||
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
|
||||
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
|
||||
/// <param name="input">The PVRTC texture data to decompress</param>
|
||||
/// <param name="xDim">X dimension (width) of the texture</param>
|
||||
/// <param name="yDim">Y dimension (height) of the texture</param>
|
||||
/// <param name="output">The decompressed texture data</param>
|
||||
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
|
||||
/// <returns>The number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressPVRTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<TOutputColor> output)
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, xDim * yDim);
|
||||
int xBlockSize = do2bitMode ? BlockX2bpp : BlockX4bpp;
|
||||
// for MBX don't allow the sizes to get too small
|
||||
int blockXDim = Math.Max(2, xDim / xBlockSize);
|
||||
@@ -65,12 +92,12 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
int blockX = (x - xBlockSize / 2);
|
||||
int blockY = (y - BlockYSize / 2);
|
||||
|
||||
blockX = LimitCoord(blockX, xDim) / xBlockSize;
|
||||
blockY = LimitCoord(blockY, yDim) / BlockYSize;
|
||||
blockX = LimitCoordinate(blockX, xDim) / xBlockSize;
|
||||
blockY = LimitCoordinate(blockY, yDim) / BlockYSize;
|
||||
|
||||
// compute the positions of the other 3 blocks
|
||||
int blockXp1 = LimitCoord(blockX + 1, blockXDim);
|
||||
int blockYp1 = LimitCoord(blockY + 1, blockYDim);
|
||||
int blockXp1 = LimitCoordinate(blockX + 1, blockXDim);
|
||||
int blockYp1 = LimitCoordinate(blockY + 1, blockYDim);
|
||||
|
||||
// map to block memory locations
|
||||
uint blocki00 = TwiddleUV((uint)blockYDim, (uint)blockXDim, (uint)blockY, (uint)blockX);
|
||||
@@ -108,12 +135,12 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
InterpolateColours(m_colors[0], m_colors[1], m_colors[2], m_colors[3], 1, do2bitMode, x, y, bSig);
|
||||
GetModulationValue(x, y, do2bitMode, modulationVals, modulationModes, out int mod, out bool doPT);
|
||||
|
||||
// compute the modulated color. Swap red and blue channel
|
||||
int position = (x + y * xDim) << 2;
|
||||
output[position + 0] = (byte)((aSig[2] * 8 + mod * (bSig[2] - aSig[2])) >> 3);
|
||||
output[position + 1] = (byte)((aSig[1] * 8 + mod * (bSig[1] - aSig[1])) >> 3);
|
||||
output[position + 2] = (byte)((aSig[0] * 8 + mod * (bSig[0] - aSig[0])) >> 3);
|
||||
output[position + 3] = doPT ? (byte)0 : (byte)((aSig[3] * 8 + mod * (bSig[3] - aSig[3])) >> 3);
|
||||
// compute the modulated color.
|
||||
byte red = (byte)((aSig[0] * 8 + mod * (bSig[0] - aSig[0])) >> 3);
|
||||
byte green = (byte)((aSig[1] * 8 + mod * (bSig[1] - aSig[1])) >> 3);
|
||||
byte blue = (byte)((aSig[2] * 8 + mod * (bSig[2] - aSig[2])) >> 3);
|
||||
byte alpha = doPT ? (byte)0 : (byte)((aSig[3] * 8 + mod * (bSig[3] - aSig[3])) >> 3);
|
||||
output[x + y * xDim].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>(red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,24 +159,10 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
/// <returns>The twiddled offset of the pixel</returns>
|
||||
private static uint TwiddleUV(uint ySize, uint xSize, uint yPos, uint xPos)
|
||||
{
|
||||
#if DEBUG
|
||||
if (yPos >= ySize)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (xPos >= xSize)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (!BitOperations.IsPow2(ySize))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (!BitOperations.IsPow2(xSize))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
#endif
|
||||
Debug.Assert(yPos < ySize);
|
||||
Debug.Assert(xPos < xSize);
|
||||
Debug.Assert(BitOperations.IsPow2(ySize));
|
||||
Debug.Assert(BitOperations.IsPow2(xSize));
|
||||
|
||||
uint minDimension;
|
||||
uint maxValue;
|
||||
@@ -164,10 +177,6 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
maxValue = yPos;
|
||||
}
|
||||
|
||||
#if DISABLE_TWINDDLING_ROUTINE
|
||||
// nasty hack to disable twiddling
|
||||
return (yPos * xSize + xPos);
|
||||
#else
|
||||
// step through all the bits in the "minimum" dimension
|
||||
uint srcBitPos = 1;
|
||||
uint dstBitPos = 1;
|
||||
@@ -195,7 +204,6 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
maxValue >>= shiftCount;
|
||||
twiddled |= (maxValue << (2 * shiftCount));
|
||||
return twiddled;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -217,36 +225,36 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
int modVal;
|
||||
if (modulationModes[y * 16 + x] == 0)
|
||||
{
|
||||
modVal = m_repVals0[modulationVals[y * 16 + x]];
|
||||
modVal = RepVals0[modulationVals[y * 16 + x]];
|
||||
}
|
||||
else if (do2bitMode)
|
||||
{
|
||||
// if this is a stored value
|
||||
if (((x ^ y) & 1) == 0)
|
||||
{
|
||||
modVal = m_repVals0[modulationVals[y * 16 + x]];
|
||||
modVal = RepVals0[modulationVals[y * 16 + x]];
|
||||
}
|
||||
// else average from the neighbours if H&V interpolation..
|
||||
else if (modulationModes[y * 16 + x] == 1)
|
||||
{
|
||||
modVal = (m_repVals0[modulationVals[(y - 1) * 16 + x]] + m_repVals0[modulationVals[(y + 1) * 16 + x]] +
|
||||
m_repVals0[modulationVals[y * 16 + x - 1]] + m_repVals0[modulationVals[y * 16 + x + 1]] + 2) / 4;
|
||||
modVal = (RepVals0[modulationVals[(y - 1) * 16 + x]] + RepVals0[modulationVals[(y + 1) * 16 + x]] +
|
||||
RepVals0[modulationVals[y * 16 + x - 1]] + RepVals0[modulationVals[y * 16 + x + 1]] + 2) / 4;
|
||||
}
|
||||
// else if H-Only
|
||||
else if (modulationModes[y * 16 + x] == 2)
|
||||
{
|
||||
modVal = (m_repVals0[modulationVals[y * 16 + x - 1]] + m_repVals0[modulationVals[y * 16 + x + 1]] + 1) / 2;
|
||||
modVal = (RepVals0[modulationVals[y * 16 + x - 1]] + RepVals0[modulationVals[y * 16 + x + 1]] + 1) / 2;
|
||||
}
|
||||
// else it's V-Only
|
||||
else
|
||||
{
|
||||
modVal = (m_repVals0[modulationVals[(y - 1) * 16 + x]] + m_repVals0[modulationVals[(y + 1) * 16 + x]] + 1) / 2;
|
||||
modVal = (RepVals0[modulationVals[(y - 1) * 16 + x]] + RepVals0[modulationVals[(y + 1) * 16 + x]] + 1) / 2;
|
||||
}
|
||||
}
|
||||
// else it's 4BPP and PT encoding
|
||||
else
|
||||
{
|
||||
modVal = m_repVals1[modulationVals[y * 16 + x]];
|
||||
modVal = RepVals1[modulationVals[y * 16 + x]];
|
||||
doPT = modulationVals[y * 16 + x] == PTIndex;
|
||||
}
|
||||
|
||||
@@ -290,16 +298,11 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
// sanity check
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (result[i] >= 256)
|
||||
{
|
||||
throw new Exception("Sanity failed");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Debug.Assert(result[0] < 256);
|
||||
Debug.Assert(result[1] < 256);
|
||||
Debug.Assert(result[2] < 256);
|
||||
Debug.Assert(result[3] < 256);
|
||||
|
||||
// convert from 5554 to 8888, do RGB 5.3 => 8
|
||||
for (int i = 0; i < 3; i++)
|
||||
@@ -308,16 +311,11 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
}
|
||||
result[3] += result[3] >> 4;
|
||||
|
||||
#if DEBUG
|
||||
// 2nd sanity check
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (result[i] >= 256)
|
||||
{
|
||||
throw new Exception("2nd sanity failed");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Debug.Assert(result[0] < 256);
|
||||
Debug.Assert(result[1] < 256);
|
||||
Debug.Assert(result[2] < 256);
|
||||
Debug.Assert(result[3] < 256);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -456,19 +454,20 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Define an expression to either wrap or clamp large or small vals to the legal coordinate range
|
||||
/// Define an expression to either wrap or clamp large or small values to the legal coordinate range.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static int LimitCoord(int value, int size)
|
||||
private static int LimitCoordinate(int value, int size, bool assumeImageTiling = true)
|
||||
{
|
||||
#if ASSUME_IMAGE_TILING
|
||||
// wrap coord
|
||||
return value & (size - 1);
|
||||
#else
|
||||
// clamp
|
||||
return h < x ? h : (x > l ? x : l);
|
||||
return Clamp(Val, 0, Size - 1);
|
||||
#endif
|
||||
if (assumeImageTiling)
|
||||
{
|
||||
// wrap coordinate
|
||||
return value & (size - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// clamp coordinate
|
||||
return int.Clamp(value, 0, size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -487,7 +486,7 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
private const int BlockX2bpp = 8;
|
||||
private const int BlockX4bpp = 4;
|
||||
|
||||
private static readonly int[] m_repVals0 = [0, 3, 5, 8];
|
||||
private static readonly int[] m_repVals1 = [0, 4, 4, 8];
|
||||
private static ReadOnlySpan<int> RepVals0 => [0, 3, 5, 8];
|
||||
private static ReadOnlySpan<int> RepVals1 => [0, 4, 4, 8];
|
||||
}
|
||||
}
|
||||
@@ -503,6 +503,7 @@ namespace AssetRipper.TextureDecoder.Rgb
|
||||
where TDestinationChannel : unmanaged
|
||||
where TDestinationColor : unmanaged, IColor<TDestinationChannel>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ReadOnlySpan<TSourceColor> sourceSpan = MemoryMarshal.Cast<byte, TSourceColor>(input).Slice(0, width * height);
|
||||
Span<TDestinationColor> destinationSpan = MemoryMarshal.Cast<byte, TDestinationColor>(output).Slice(0, width * height);
|
||||
Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>(sourceSpan, destinationSpan);
|
||||
@@ -516,9 +517,34 @@ namespace AssetRipper.TextureDecoder.Rgb
|
||||
where TDestinationChannel : unmanaged
|
||||
where TDestinationColor : unmanaged, IColor<TDestinationChannel>
|
||||
{
|
||||
for (int i = 0; i < sourceSpan.Length; i++)
|
||||
if (sourceSpan.Length is 0)
|
||||
{
|
||||
destinationSpan[i] = sourceSpan[i].Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>();
|
||||
// Do nothing if the source span is empty
|
||||
}
|
||||
else if (destinationSpan.Length < sourceSpan.Length)
|
||||
{
|
||||
ThrowDestinationSpanNotLargeEnough();
|
||||
}
|
||||
else if (typeof(TSourceColor) != typeof(TDestinationColor))
|
||||
{
|
||||
for (int i = 0; i < sourceSpan.Length; i++)
|
||||
{
|
||||
destinationSpan[i] = sourceSpan[i].Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>();
|
||||
}
|
||||
}
|
||||
else if (Unsafe.AreSame(in sourceSpan[0], ref Unsafe.As<TDestinationColor, TSourceColor>(ref destinationSpan[0])))
|
||||
{
|
||||
// Do nothing because the source and destination point to the same memory
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: This assumes that the source and destination will never overlap.
|
||||
sourceSpan.CopyTo(MemoryMarshal.Cast<TDestinationColor, TSourceColor>(destinationSpan));
|
||||
}
|
||||
|
||||
static void ThrowDestinationSpanNotLargeEnough()
|
||||
{
|
||||
throw new ArgumentException("Destination span is not large enough to hold the converted data.", nameof(destinationSpan));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,5 +19,13 @@
|
||||
throw new InvalidOperationException($"Not enough space. {spaceRequired} is required, but only {spaceAvailable} is available");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ThrowIfNotLittleEndian()
|
||||
{
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,9 @@
|
||||
using AssetRipper.TextureDecoder.Rgb;
|
||||
using AssetRipper.TextureDecoder.Rgb.Formats;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Yuy2
|
||||
{
|
||||
public static class Yuy2Decoder
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompress a YUY2 image
|
||||
/// </summary>
|
||||
/// <param name="input">Input buffer containing the compressed image.</param>
|
||||
/// <param name="width">Pixel width of the image.</param>
|
||||
/// <param name="height">Pixel height of the image.</param>
|
||||
/// <param name="output">An output buffer of size 4 * width * height.</param>
|
||||
/// <returns>Number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressYUY2(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
return DecompressYUY2<ColorBGRA32, byte>(input, width, height, out output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a YUY2 image
|
||||
/// </summary>
|
||||
/// <param name="input">Input buffer containing the compressed image.</param>
|
||||
/// <param name="width">Pixel width of the image.</param>
|
||||
/// <param name="height">Pixel height of the image.</param>
|
||||
/// <param name="output">An output buffer. Must be at least 4 * width * height.</param>
|
||||
/// <returns>Number of bytes read from <paramref name="input"/></returns>
|
||||
public static int DecompressYUY2(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
return DecompressYUY2<ColorBGRA32, byte>(input, width, height, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a YUY2 image
|
||||
/// </summary>
|
||||
@@ -63,6 +36,7 @@ namespace AssetRipper.TextureDecoder.Yuy2
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressYUY2<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>Latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||
|
||||
<Version>2.2.0</Version>
|
||||
<Version>2.4.0</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<Copyright>Copyright © ds5678</Copyright>
|
||||
<Authors>ds5678</Authors>
|
||||
|
||||
Reference in new issue
Block a user