Compare commits

...
16 Commits
Author SHA1 Message Date
ds5678 dbfe5b2979 Bump project version to 2.4.0
Updated the `<Version>` property in `Directory.Build.props`
from `2.3.0` to `2.4.0`. This version increment reflects
new features, improvements, or bug fixes introduced in
the project.
2025-10-06 02:13:12 -07:00
ds5678 168098c29e Update BenchmarkDotNet and test dependencies
Updated the `BenchmarkDotNet` package in `AssetRipper.TextureDecoder.Benchmarks.csproj` to version `0.15.4`.

Updated test-related dependencies in `AssetRipper.TextureDecoder.Tests.csproj`:
- `Microsoft.NET.Test.Sdk` to version `17.14.1`
- `NUnit` to version `4.4.0`
- `NUnit3TestAdapter` to version `5.1.0`
- `NUnit.Analyzers` to version `4.10.0`

Retained `<PrivateAssets>` and `<IncludeAssets>` metadata for `NUnit.Analyzers`. No functional changes were made to the `coverlet.collector` package.
2025-10-06 02:11:30 -07:00
dependabot[bot] 8b493e5650 Bump actions/setup-dotnet from 4.3.1 to 5.0.0 (#101)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.3.1 to 5.0.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4.3.1...v5.0.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 02:09:41 -07:00
dependabot[bot] 9e60f004df Bump actions/checkout from 4 to 5 (#100)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 02:09:29 -07:00
ds5678 324f8eb8f4 Refactor: Replace custom WriteAllBytes with File API
Removed the custom WriteAllBytes method from the Program class,
which handled writing a ReadOnlySpan<byte> to a file. Updated
the code to use the built-in File.WriteAllBytes method instead.
This change simplifies the codebase by leveraging .NET's
standard library functionality and eliminates redundant code.
2025-10-06 02:07:20 -07:00
ds5678 b39200e3ff Generic methods for Etc decoding 2025-10-06 02:05:09 -07:00
ds5678 ed8ffde7d0 Generic methods for ASTC decoding 2025-10-06 00:39:26 -07:00
ds5678 e70c6d961c Generic methods for Atc decoding 2025-10-06 00:27:08 -07:00
ds5678 93ad0eeccf Generic methods for Bc decoding 2025-10-06 00:08:51 -07:00
ds5678 b5b6b5aca8 Remove non-generic methods for DXT and YUY2 2025-10-05 23:29:31 -07:00
ds5678 2c2f9486f9 Refactor coordinate handling in PvrtcDecoder
Replaced `#define ASSUME_IMAGE_TILING` with a runtime parameter
to control coordinate wrapping or clamping behavior. Renamed
`LimitCoord` to `LimitCoordinate` for clarity and added an
optional `assumeImageTiling` parameter. Updated all method
calls to reflect the new name and behavior. Removed inline
and optimization attributes from `LimitCoordinate`. Improved
method documentation for better readability.
2025-10-05 23:21:01 -07:00
ds5678 cd60c44ba5 Remove DISABLE_TWINDDLING_ROUTINE directive
The `DISABLE_TWINDDLING_ROUTINE` preprocessor directive and its associated code have been removed from `PvrtcDecoder.cs`. This directive was previously used to conditionally disable the twiddling routine with a simplified calculation.

The removal simplifies the codebase by eliminating conditional logic, ensuring the twiddling routine is always executed. The `#else` block containing the twiddling logic remains intact, and the `#endif` directive has been removed as it is no longer necessary.
2025-10-05 23:13:09 -07:00
ds5678 09f605e45d Remove non-generic PVRTC methods 2025-10-05 23:11:47 -07:00
ds5678 40ef1f6989 Generic color support for PVRTC 2025-10-05 23:05:15 -07:00
ds5678 eeffa5897d Simplify path handling in the console app 2025-10-05 22:44:07 -07:00
ds5678 d45e129e46 More efficient RgbConverter.Convert 2025-10-05 22:20:14 -07:00
26 changed files with 631 additions and 526 deletions

No files matched your search

+2 -2
View File
@@ -12,9 +12,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Setup .NET
uses: actions/setup-dotnet@v4.3.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: 9.0.x
- name: Restore dependencies
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
</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);
}
}
@@ -5,10 +5,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.6.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>
@@ -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 _));
}
}
+5 -3
View File
@@ -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));
+17 -17
View File
@@ -27,8 +27,8 @@ namespace AssetRipper.TextureDecoder.Tests
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);
}
@@ -42,8 +42,8 @@ namespace AssetRipper.TextureDecoder.Tests
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);
}
@@ -57,8 +57,8 @@ namespace AssetRipper.TextureDecoder.Tests
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);
}
@@ -69,7 +69,7 @@ namespace AssetRipper.TextureDecoder.Tests
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));
}
@@ -77,7 +77,7 @@ namespace AssetRipper.TextureDecoder.Tests
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));
}
@@ -103,7 +103,7 @@ namespace AssetRipper.TextureDecoder.Tests
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1").AsSpan()[..Bc1.BlockSize];
int bytesRead = Bc1.Decompress(data, width, height, out byte[] decodedData);
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>()));
});
@@ -115,7 +115,7 @@ namespace AssetRipper.TextureDecoder.Tests
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3").AsSpan()[..Bc2.BlockSize];
int bytesRead = Bc2.Decompress(data, width, height, out byte[] decodedData);
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>()));
});
@@ -127,7 +127,7 @@ namespace AssetRipper.TextureDecoder.Tests
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5").AsSpan()[..Bc3.BlockSize];
int bytesRead = Bc3.Decompress(data, width, height, out byte[] decodedData);
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>()));
});
@@ -139,7 +139,7 @@ namespace AssetRipper.TextureDecoder.Tests
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4").AsSpan()[..Bc4.BlockSize];
int bytesRead = Bc4.Decompress(data, width, height, out byte[] decodedData);
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>()));
});
@@ -151,7 +151,7 @@ namespace AssetRipper.TextureDecoder.Tests
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5").AsSpan()[..Bc5.BlockSize];
int bytesRead = Bc5.Decompress(data, width, height, out byte[] decodedData);
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>()));
});
@@ -163,7 +163,7 @@ namespace AssetRipper.TextureDecoder.Tests
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc6h_best").AsSpan()[..Bc6h.BlockSize];
int bytesRead = Bc6h.Decompress(data, width, height, false, out byte[] decodedData);
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>()));
});
@@ -175,7 +175,7 @@ namespace AssetRipper.TextureDecoder.Tests
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>()));
});
@@ -184,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);
}
@@ -192,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);
}
+6 -4
View File
@@ -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));
+11 -11
View File
@@ -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));
+28 -25
View File
@@ -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)
{
+54 -29
View File
@@ -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;
}
@@ -160,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);
}
}
@@ -197,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));
}
}
}
+19 -6
View File
@@ -26,14 +26,28 @@ public static class Bc1
/// </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)
@@ -41,11 +55,10 @@ public static class Bc1
for (int j = 0; j < width; j += BlockWidth)
{
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
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;
}
+20 -7
View File
@@ -26,26 +26,39 @@ public static class Bc2
/// </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)
{
BcHelpers.DecompressBc2(input.Slice(inputOffset), buffer);
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
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;
}
+20 -7
View File
@@ -26,26 +26,39 @@ public static class Bc3
/// </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)
{
BcHelpers.DecompressBc3(input.Slice(inputOffset), buffer);
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
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;
}
+20 -11
View File
@@ -27,30 +27,39 @@ public static class Bc4
/// </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 naturalSize = width * height * PixelSize;
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
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)
{
BcHelpers.DecompressBc4(input.Slice(inputOffset), buffer);
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
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>(naturalPixels, width, height, output);
ArrayPool<byte>.Shared.Return(rentedArray);
return inputOffset;
}
+20 -11
View File
@@ -27,30 +27,39 @@ public static class Bc5
/// </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 naturalSize = width * height * PixelSize;
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
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)
{
BcHelpers.DecompressBc5(input.Slice(inputOffset), buffer);
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
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>(naturalPixels, width, height, output);
ArrayPool<byte>.Shared.Return(rentedArray);
return inputOffset;
}
+20 -17
View File
@@ -27,36 +27,39 @@ public static class Bc6h
/// </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 naturalSize = width * height * PixelSize;
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
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 += BlockHeight)
{
for (int j = 0; j < width; j += BlockWidth)
{
BcHelpers.DecompressBc6h(
input.Slice(inputOffset, BlockSize),
buffer,
isSigned);
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
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>(naturalPixels, width, height, output);
ArrayPool<byte>.Shared.Return(rentedArray);
return inputOffset;
}
+20 -12
View File
@@ -26,31 +26,39 @@ public static class Bc7
/// </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>
{
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
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)
{
BcHelpers.DecompressBc7(
input.Slice(inputOffset, BlockSize),
buffer);
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
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;
}
+15 -9
View File
@@ -1,4 +1,5 @@
using System.Buffers.Binary;
using AssetRipper.TextureDecoder.Rgb;
using System.Buffers.Binary;
using System.Diagnostics;
namespace AssetRipper.TextureDecoder.Bc;
@@ -1094,17 +1095,22 @@ internal static class BcHelpers
/// <summary>
/// This is for copying a decoded block to the output buffer. It handles the pitch and non-4x4 blocks (if necessary).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void CopyBufferToOutput(Span<byte> buffer, Span<byte> output, int width, int height, int w, int h, int blockWidth, int blockHeight, int pixelSize)
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 = Math.Min(h + blockHeight, height);
int wEnd = Math.Min(w + blockWidth, width);
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) + w) * pixelSize;
int outputBufferOffset = ((i - h) * blockWidth) * pixelSize;
int copyLength = (wEnd - w) * pixelSize;
buffer.Slice(outputBufferOffset, copyLength).CopyTo(output.Slice(outputOffset, copyLength));
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>();
}
}
}
}
@@ -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>
@@ -128,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>
@@ -260,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>
+215 -126
View File
@@ -1,16 +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)
{
@@ -20,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>();
}
}
}
@@ -43,15 +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)
{
@@ -61,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>();
}
}
}
@@ -84,15 +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)
{
@@ -102,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>();
}
}
}
@@ -125,15 +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)
{
@@ -143,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>();
}
}
}
@@ -167,15 +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)
{
@@ -185,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>();
}
}
}
@@ -212,15 +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)
{
@@ -230,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>();
}
}
}
@@ -257,15 +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;
@@ -275,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>();
}
}
}
@@ -303,15 +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)
{
@@ -321,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>();
}
}
}
@@ -352,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 =
@@ -401,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];
@@ -431,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),
@@ -442,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
@@ -472,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),
@@ -482,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
@@ -515,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);
}
}
}
@@ -546,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);
}
}
}
@@ -578,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)
{
@@ -599,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];
@@ -620,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),
@@ -631,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
@@ -662,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),
@@ -672,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
@@ -706,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);
}
}
}
@@ -733,21 +823,26 @@ 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];
@@ -757,18 +852,18 @@ 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];
@@ -776,17 +871,17 @@ namespace AssetRipper.TextureDecoder.Etc
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]);
@@ -794,13 +889,13 @@ namespace AssetRipper.TextureDecoder.Etc
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);
}
}
@@ -824,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)
{
@@ -837,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)]
@@ -909,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 ReadOnlySpan<uint> PunchthroughMaskTable => [0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF];
private static ReadOnlySpan<bool> PunchthroughBooleanTable => [true, true, false, true];
}
}
@@ -1,39 +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);
@@ -66,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);
@@ -109,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);
}
}
@@ -133,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;
@@ -165,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;
@@ -196,7 +204,6 @@ namespace AssetRipper.TextureDecoder.Pvrtc
maxValue >>= shiftCount;
twiddled |= (maxValue << (2 * shiftCount));
return twiddled;
#endif
}
/// <summary>
@@ -291,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++)
@@ -309,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>
@@ -457,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>
+27 -2
View File
@@ -517,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));
}
}
@@ -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>
+1 -1
View File
@@ -6,7 +6,7 @@
<IsAotCompatible>true</IsAotCompatible>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<Version>2.3.0</Version>
<Version>2.4.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<Copyright>Copyright © ds5678</Copyright>
<Authors>ds5678</Authors>