Compare commits

...
25 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
ds5678 d57e3461ff Update version to 2.3.0 in Directory.Build.props
Bump the version number from 2.2.1 to 2.3.0 in the
Directory.Build.props file to reflect the latest changes.
2025-03-30 22:48:41 -07:00
ds5678 1d2d19c9ef Support Bc images with non-standard sizes
* Resolves #39
2025-03-27 01:37:00 -07:00
ds5678 f3ba84589d Update package references
Updated `AssetRipper.Text.SourceGeneration` to version `1.2.2` in `AssetRipper.TextureDecoder.TestGenerator` and `AssetRipper.TextureDecoder.ColorGenerator`.

In `AssetRipper.TextureDecoder.Tests.csproj`, the following packages were updated:
- `Microsoft.NET.Test.Sdk` from `17.10.0` to `17.13.0`
- `NUnit` from `4.1.0` to `4.3.2`
- `NUnit3TestAdapter` from `4.6.0` to `5.0.0`
- `coverlet.collector` from `6.0.2` to `6.0.4`
2025-03-26 23:44:19 -07:00
ds5678 7cee390b2e Bump to .NET 9 2025-03-26 23:41:27 -07:00
dependabot[bot] 024ac9eb8c Bump actions/setup-dotnet from 4.0.1 to 4.3.0 (#96)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.0.1 to 4.3.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4.0.1...v4.3.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-26 23:39:28 -07:00
dependabot[bot] d47fe4dcfb Bump NUnit.Analyzers from 4.3.0 to 4.4.0
Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/nunit/nunit.analyzers/releases)
- [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit.analyzers/compare/4.3.0...4.4.0)

---
updated-dependencies:
- dependency-name: NUnit.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-11-13 17:57:48 -08:00
ds5678 7d028ce3f5 Guard against big endian 2024-10-22 19:21:11 -07:00
dependabot[bot] beb819daa4 Bump NUnit.Analyzers from 4.2.0 to 4.3.0
Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/nunit/nunit.analyzers/releases)
- [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit.analyzers/compare/4.2.0...4.3.0)

---
updated-dependencies:
- dependency-name: NUnit.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-11 19:37:00 -07:00
dependabot[bot] c9d338f159 Bump BenchmarkDotNet from 0.13.12 to 0.14.0
Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.12 to 0.14.0.
- [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases)
- [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.12...v0.14.0)

---
updated-dependencies:
- dependency-name: BenchmarkDotNet
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-06 19:33:32 -07:00
29 changed files with 895 additions and 571 deletions

No files matched your search

+3 -3
View File
@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Setup .NET
uses: actions/setup-dotnet@v4.0.1
uses: actions/setup-dotnet@v5.0.0
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
</ItemGroup>
<ItemGroup>
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" />
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" />
</ItemGroup>
<ItemGroup>
@@ -52,43 +52,40 @@ internal static partial class Program
DecodeRgb(data, width, height, args5, bitmap.Bits);
break;
case "yuy2":
Yuy2Decoder.DecompressYUY2(data, width, height, bitmap.Bits);
Yuy2Decoder.DecompressYUY2<ColorBGRA32, byte>(data, width, height, bitmap.Bits);
break;
default:
throw new NotSupportedException(inputType);
}
string dirPath = Path.GetDirectoryName(path) ?? Environment.CurrentDirectory;
string name = Path.GetFileName(path);
switch (outputType)
{
case "bgra":
WriteAllBytes(Path.Combine(dirPath, name + ".bgra"), bitmap.Bits);
File.WriteAllBytes(path + ".bgra", bitmap.Bits);
break;
case "bmp":
bitmap.FlipY();
bitmap.SaveAsBmp(Path.Combine(dirPath, name + ".bmp"));
bitmap.SaveAsBmp(path + ".bmp");
break;
case "exr":
bitmap.FlipY();
bitmap.SaveAsExr(Path.Combine(dirPath, name + ".exr"));
bitmap.SaveAsExr(path + ".exr");
break;
case "hdr":
bitmap.FlipY();
bitmap.SaveAsHdr(Path.Combine(dirPath, name + ".hdr"));
bitmap.SaveAsHdr(path + ".hdr");
break;
case "jpg":
bitmap.FlipY();
bitmap.SaveAsJpg(Path.Combine(dirPath, name + ".jpg"));
bitmap.SaveAsJpg(path + ".jpg");
break;
case "png":
bitmap.FlipY();
bitmap.SaveAsPng(Path.Combine(dirPath, name + ".png"));
bitmap.SaveAsPng(path + ".png");
break;
case "tga":
bitmap.FlipY();
bitmap.SaveAsTga(Path.Combine(dirPath, name + ".tga"));
bitmap.SaveAsTga(path + ".tga");
break;
default:
throw new NotSupportedException(outputType);
@@ -108,7 +105,7 @@ internal static partial class Program
Console.WriteLine("Arg at index 6 : blockYSize");
int blockXSize = int.Parse(blockXSizeString);
int blockYSize = int.Parse(blockYSizeString);
AstcDecoder.DecodeASTC(input, width, height, blockXSize, blockYSize, output);
AstcDecoder.DecodeASTC<ColorBGRA32, byte>(input, width, height, blockXSize, blockYSize, output);
}
private static void DecodeAtc(ReadOnlySpan<byte> input, int width, int height, string modeString, Span<byte> output)
@@ -120,10 +117,10 @@ internal static partial class Program
switch (mode)
{
case 0:
AtcDecoder.DecompressAtcRgb4(input, width, height, output);
AtcDecoder.DecompressAtcRgb4<ColorBGRA32, byte>(input, width, height, output);
break;
case 1:
AtcDecoder.DecompressAtcRgba8(input, width, height, output);
AtcDecoder.DecompressAtcRgba8<ColorBGRA32, byte>(input, width, height, output);
break;
default:
@@ -146,25 +143,25 @@ internal static partial class Program
switch (mode)
{
case 1:
Bc1.Decompress(input, width, height, output);
Bc1.Decompress<ColorBGRA32, byte>(input, width, height, output);
break;
case 2:
Bc2.Decompress(input, width, height, output);
Bc2.Decompress<ColorBGRA32, byte>(input, width, height, output);
break;
case 3:
Bc3.Decompress(input, width, height, output);
Bc3.Decompress<ColorBGRA32, byte>(input, width, height, output);
break;
case 4:
Bc4.Decompress(input, width, height, output);
Bc4.Decompress<ColorBGRA32, byte>(input, width, height, output);
break;
case 5:
Bc5.Decompress(input, width, height, output);
Bc5.Decompress<ColorBGRA32, byte>(input, width, height, output);
break;
case 6:
Bc6h.Decompress(input, width, height, bool.Parse(isSignedString), output);
Bc6h.Decompress<ColorBGRA32, byte>(input, width, height, bool.Parse(isSignedString), output);
break;
case 7:
Bc7.Decompress(input, width, height, output);
Bc7.Decompress<ColorBGRA32, byte>(input, width, height, output);
break;
default:
@@ -182,13 +179,13 @@ internal static partial class Program
switch (mode)
{
case 0:
DxtDecoder.DecompressDXT1(input, width, height, output);
DxtDecoder.DecompressDXT1<ColorBGRA32, byte>(input, width, height, output);
break;
case 1:
DxtDecoder.DecompressDXT3(input, width, height, output);
DxtDecoder.DecompressDXT3<ColorBGRA32, byte>(input, width, height, output);
break;
case 2:
DxtDecoder.DecompressDXT5(input, width, height, output);
DxtDecoder.DecompressDXT5<ColorBGRA32, byte>(input, width, height, output);
break;
default:
@@ -211,28 +208,28 @@ internal static partial class Program
switch (mode)
{
case 0:
EtcDecoder.DecompressETC(input, width, height, output);
EtcDecoder.DecompressETC<ColorBGRA32, byte>(input, width, height, output);
break;
case 1:
EtcDecoder.DecompressETC2(input, width, height, output);
EtcDecoder.DecompressETC2<ColorBGRA32, byte>(input, width, height, output);
break;
case 2:
EtcDecoder.DecompressETC2A1(input, width, height, output);
EtcDecoder.DecompressETC2A1<ColorBGRA32, byte>(input, width, height, output);
break;
case 3:
EtcDecoder.DecompressETC2A8(input, width, height, output);
EtcDecoder.DecompressETC2A8<ColorBGRA32, byte>(input, width, height, output);
break;
case 4:
EtcDecoder.DecompressEACRUnsigned(input, width, height, output);
EtcDecoder.DecompressEACRUnsigned<ColorBGRA32, byte>(input, width, height, output);
break;
case 5:
EtcDecoder.DecompressEACRSigned(input, width, height, output);
EtcDecoder.DecompressEACRSigned<ColorBGRA32, byte>(input, width, height, output);
break;
case 6:
EtcDecoder.DecompressEACRGUnsigned(input, width, height, output);
EtcDecoder.DecompressEACRGUnsigned<ColorBGRA32, byte>(input, width, height, output);
break;
case 7:
EtcDecoder.DecompressEACRGSigned(input, width, height, output);
EtcDecoder.DecompressEACRGSigned<ColorBGRA32, byte>(input, width, height, output);
break;
default:
@@ -244,13 +241,6 @@ internal static partial class Program
{
Console.WriteLine("Arg at index 5 : 2bitMode");
bool do2bit = bool.Parse(do2bitModeString);
PvrtcDecoder.DecompressPVRTC(input, width, height, do2bit, output);
}
public static void WriteAllBytes(string path, ReadOnlySpan<byte> bytes)
{
ArgumentException.ThrowIfNullOrEmpty(path, nameof(path));
using Microsoft.Win32.SafeHandles.SafeFileHandle sfh = File.OpenHandle(path, FileMode.Create, FileAccess.Write, FileShare.Read);
RandomAccess.Write(sfh, bytes, 0);
PvrtcDecoder.DecompressPVRTC<ColorBGRA32, byte>(input, width, height, do2bit, output);
}
}
@@ -11,7 +11,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" />
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" />
</ItemGroup>
</Project>
@@ -5,14 +5,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.10.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -1,4 +1,5 @@
using AssetRipper.TextureDecoder.Astc;
using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Tests;
@@ -67,7 +68,7 @@ public sealed class AstcTests
private static void AssertCorrectDecompression<T>(int blockWidth, int blockHeight, double maxMeanDeviation, double maxStandardDeviation) where T : ITexture
{
ReadOnlySpan<byte> data = T.Data;
int bytesRead = AstcDecoder.DecodeASTC(data, T.Width, T.Height, blockWidth, blockHeight, out byte[] decompressedData);
int bytesRead = AstcDecoder.DecodeASTC<ColorBGRA32, byte>(data, T.Width, T.Height, blockWidth, blockHeight, out byte[] decompressedData);
if (!T.Mips)
{
Assert.That(bytesRead, Is.EqualTo(data.Length));
@@ -78,6 +79,6 @@ public sealed class AstcTests
[TestCase(new byte[16] { 83, 1, 147, 104, 198, 173, 55, 116, 182, 66, 105, 11, 102, 43, 148, 125 }, 8, 8, TestName = "GetBits Should not throw in DecodeIntseq")]
public void DecodesBlockWithoutThrowing(byte[] block, int blockWidth, int blockHeight)
{
Assert.DoesNotThrow(() => AstcDecoder.DecodeASTC(block, blockWidth, blockHeight, blockWidth, blockHeight, out _));
Assert.DoesNotThrow(() => AstcDecoder.DecodeASTC<ColorBGRA32, byte>(block, blockWidth, blockHeight, blockWidth, blockHeight, out _));
}
}
+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));
+91 -20
View File
@@ -21,14 +21,14 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressDXT1Test()
public void Decompress_BC1()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1");
int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4 }) //mipmaps 2 and 1 cause a buffer overrun for the output
{
int bytesRead = Bc1.Decompress(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT1(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
int bytesRead = Bc1.Decompress<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT1<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
totalBytesRead += bytesRead;
AssertAlmostEqual(dxtDecodedData, bcDecodedData);
}
@@ -36,14 +36,14 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressDXT3Test()
public void Decompress_BC2()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3");
int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4 }) //mipmaps 2 and 1 cause a buffer overrun for the output
{
int bytesRead = Bc2.Decompress(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT3(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
int bytesRead = Bc2.Decompress<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT3<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
totalBytesRead += bytesRead;
AssertAlmostEqual(dxtDecodedData, bcDecodedData);
}
@@ -51,14 +51,14 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressDXT5Test()
public void Decompress_BC3()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5");
int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4 }) //mipmaps 2 and 1 cause a buffer overrun for the output
{
int bytesRead = Bc3.Decompress(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT5(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
int bytesRead = Bc3.Decompress<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT5<ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
totalBytesRead += bytesRead;
AssertAlmostEqual(dxtDecodedData, bcDecodedData);
}
@@ -66,25 +66,25 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressBC4Test()
public void Decompress_BC4()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4");
int bytesRead = Bc4.Decompress(data, 512, 512, out _);
int bytesRead = Bc4.Decompress<ColorBGRA32, byte>(data, 512, 512, out _);
Assert.That(bytesRead, Is.EqualTo(data.Length));
}
[Test]
public void DecompressBC5Test()
public void Decompress_BC5()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5");
int bytesRead = Bc5.Decompress(data, 512, 512, out _);
int bytesRead = Bc5.Decompress<ColorBGRA32, byte>(data, 512, 512, out _);
Assert.That(bytesRead, Is.EqualTo(data.Length));
}
[TestCase("test.bc6h_fast")]
[TestCase("test.bc6h_normal")]
[TestCase("test.bc6h_best")]
public void DecompressBC6HTest(string fileName)
public void Decompress_BC6H(string fileName)
{
AssertCorrectBC6HDecompression(TestFileFolders.BcTestFiles + fileName, 512, 512, false);
}
@@ -92,19 +92,90 @@ namespace AssetRipper.TextureDecoder.Tests
[TestCase("test.bc7_fast")]
[TestCase("test.bc7_normal")]
[TestCase("test.bc7_best")]
public void DecompressBC7Test(string fileName)
public void Decompress_BC7(string fileName)
{
AssertCorrectBC7Decompression(TestFileFolders.BcTestFiles + fileName, 512, 512);
}
[Test]
[Ignore("Not implemented")]
public void BC7PartialBlockTest([Range(1, 4)] int width, [Range(1, 4)] int height)
public void PartialBlock_BC1([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1").AsSpan()[..Bc1.BlockSize];
int bytesRead = Bc1.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC2([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3").AsSpan()[..Bc2.BlockSize];
int bytesRead = Bc2.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC3([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5").AsSpan()[..Bc3.BlockSize];
int bytesRead = Bc3.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC4([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4").AsSpan()[..Bc4.BlockSize];
int bytesRead = Bc4.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC5([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5").AsSpan()[..Bc5.BlockSize];
int bytesRead = Bc5.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC6H([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc6h_best").AsSpan()[..Bc6h.BlockSize];
int bytesRead = Bc6h.Decompress<ColorBGRA32, byte>(data, width, height, false, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC7([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc7_best").AsSpan()[..Bc7.BlockSize];
int bytesRead = Bc7.Decompress(data, width, height, out byte[] decodedData);
int bytesRead = Bc7.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
@@ -113,7 +184,7 @@ namespace AssetRipper.TextureDecoder.Tests
private static void AssertCorrectBC6HDecompression(string path, int width, int height, bool isSigned)
{
ReadOnlySpan<byte> data = File.ReadAllBytes(path);
int bytesRead = Bc6h.Decompress(data, width, height, isSigned, out byte[] decodedData);
int bytesRead = Bc6h.Decompress<ColorBGRA32, byte>(data, width, height, isSigned, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
ByteArrayDeviation.AssertMinimalDeviation(decodedData, originalBgra32LogoData, MaxMeanDeviationBc6h, MaxStandardDeviationBc6h);
}
@@ -121,7 +192,7 @@ namespace AssetRipper.TextureDecoder.Tests
private static void AssertCorrectBC7Decompression(string path, int width, int height)
{
ReadOnlySpan<byte> data = File.ReadAllBytes(path);
int bytesRead = Bc7.Decompress(data, width, height, out byte[] decodedData);
int bytesRead = Bc7.Decompress<ColorBGRA32, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
ByteArrayDeviation.AssertMinimalDeviation(decodedData, originalBgra32LogoData, MaxMeanDeviationBc7, MaxStandardDeviationBc7);
}
+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)
{
+55 -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;
}
@@ -121,6 +152,7 @@ namespace AssetRipper.TextureDecoder.Atc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static T ReadAtOffset<T>(this ReadOnlySpan<byte> input, int offset) where T : unmanaged
{
ThrowHelper.ThrowIfNotLittleEndian();
return MemoryMarshal.Read<T>(input.Slice(offset));
}
@@ -159,9 +191,9 @@ namespace AssetRipper.TextureDecoder.Atc
colors[13] = Extend((c1 >> 5) & 0x3F, 6, 8);
colors[14] = Extend((c1 >> 11) & 0x1F, 5, 8);
colors[4] = Math.Max(0, colors[8] - colors[12] / 4);
colors[5] = Math.Max(0, colors[9] - colors[13] / 4);
colors[6] = Math.Max(0, colors[10] - colors[14] / 4);
colors[4] = int.Max(0, colors[8] - colors[12] / 4);
colors[5] = int.Max(0, colors[9] - colors[13] / 4);
colors[6] = int.Max(0, colors[10] - colors[14] / 4);
}
}
@@ -196,11 +228,5 @@ namespace AssetRipper.TextureDecoder.Atc
// bit-pattern replicating scaling (can at most double the bits)
return (value << (to - from)) | (value >> (from * 2 - to));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static uint Color(int r, int g, int b, int a)
{
return unchecked((uint)(r << 16 | g << 8 | b | a << 24));
}
}
}
+39 -9
View File
@@ -9,26 +9,56 @@ public static class Bc1
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 8;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
return Decompress(input, width, height, output);
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), output.Slice(outputOffset), width * 4);
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
return inputOffset;
}
+39 -9
View File
@@ -9,26 +9,56 @@ public static class Bc2
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 16;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
return Decompress(input, width, height, output);
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
BcHelpers.DecompressBc2(input.Slice(inputOffset), output.Slice(outputOffset), width * 4);
BcHelpers.DecompressBc2(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
return inputOffset;
}
+39 -9
View File
@@ -9,26 +9,56 @@ public static class Bc3
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 16;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
return Decompress(input, width, height, output);
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
BcHelpers.DecompressBc3(input.Slice(inputOffset), output.Slice(outputOffset), width * 4);
BcHelpers.DecompressBc3(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
return inputOffset;
}
+39 -13
View File
@@ -10,30 +10,56 @@ public static class Bc4
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 8;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorR<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
return Decompress(input, width, height, output);
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
int bufferSize = width * height * Unsafe.SizeOf<ColorR<byte>>();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
ThrowHelper.ThrowIfNotLittleEndian();
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int bufferOffset = ((i * width) + j) * Unsafe.SizeOf<ColorR<byte>>();
BcHelpers.DecompressBc4(input.Slice(inputOffset), buffer.Slice(bufferOffset), width);
BcHelpers.DecompressBc4(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.CopyBufferToOutput<ColorR<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorR<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
return inputOffset;
}
+39 -13
View File
@@ -10,30 +10,56 @@ public static class Bc5
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 16;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRG<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
return Decompress(input, width, height, output);
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
int bufferSize = width * height * Unsafe.SizeOf<ColorRG<byte>>();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
ThrowHelper.ThrowIfNotLittleEndian();
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int bufferOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRG<byte>>();
BcHelpers.DecompressBc5(input.Slice(inputOffset), buffer.Slice(bufferOffset), width * 2);
BcHelpers.DecompressBc5(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.CopyBufferToOutput<ColorRG<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRG<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
return inputOffset;
}
+39 -17
View File
@@ -10,34 +10,56 @@ public static class Bc6h
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 16;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGB<Half>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, bool isSigned, out byte[] output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, bool isSigned, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
return Decompress(input, width, height, isSigned, output);
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, isSigned, output);
}
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, bool isSigned, Span<byte> output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, bool isSigned, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
int bufferSize = width * height * Unsafe.SizeOf<ColorRGB<Half>>();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
ThrowHelper.ThrowIfNotLittleEndian();
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, isSigned, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, bool isSigned, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGB<Half>>();
BcHelpers.DecompressBc6h(
input.Slice(inputOffset, BlockSize),
buffer.Slice(outputOffset),
width * 3,
isSigned);
BcHelpers.DecompressBc6h(input.Slice(inputOffset, BlockSize), buffer, isSigned);
BcHelpers.CopyBufferToOutput<ColorRGB<Half>, Half, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGB<Half>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorRGB<Half>, Half, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
return inputOffset;
}
+25 -10
View File
@@ -21,29 +21,44 @@ public static class Bc7
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<ColorBGRA32>()];
return Decompress(input, width, height, output);
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int Decompress<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * PixelSize;
BcHelpers.DecompressBc7(
input.Slice(inputOffset, BlockSize),
output.Slice(outputOffset),
width * 4);
BcHelpers.DecompressBc7(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
return inputOffset;
}
+54 -37
View File
@@ -1,39 +1,53 @@
using System.Buffers.Binary;
using AssetRipper.TextureDecoder.Rgb;
using System.Buffers.Binary;
using System.Diagnostics;
namespace AssetRipper.TextureDecoder.Bc;
internal static class BcHelpers
{
public static void DecompressBc1(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc1(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc1.DecodedBlockSize);
int destinationPitch = 4 * 4;
ColorBlock(compressedBlock, decompressedBlock, destinationPitch, 0);
}
public static void DecompressBc2(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc2(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc2.DecodedBlockSize);
int destinationPitch = 4 * 4;
ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
SharpAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch);
}
public static void DecompressBc3(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc3(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc3.DecodedBlockSize);
int destinationPitch = 4 * 4;
ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
SmoothAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch, 4);
}
public static void DecompressBc4(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc4(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc4.DecodedBlockSize);
int destinationPitch = 4 * 1;
SmoothAlphaBlock(compressedBlock, decompressedBlock, destinationPitch, 1);
}
public static void DecompressBc5(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc5(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc5.DecodedBlockSize);
int destinationPitch = 4 * 2;
SmoothAlphaBlock(compressedBlock, decompressedBlock, destinationPitch, 2);
SmoothAlphaBlock(compressedBlock.Slice(8), decompressedBlock.Slice(1), destinationPitch, 2);
}
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch, bool isSigned)
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, bool isSigned)
{
Debug.Assert(decompressedBlock.Length == Bc6h.DecodedBlockSize);
BitStream bstream = new(compressedBlock);
Span<uint> r = stackalloc uint[4]; // wxyz
Span<uint> g = stackalloc uint[4];
@@ -448,15 +462,7 @@ internal static class BcHelpers
// Do not use these in your encoder. If the hardware is passed blocks
// with one of these modes specified, the resulting decompressed block
// must contain all zeroes in all channels except for the alpha channel.
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
Span<byte> pixel = decompressedBlock.Slice(decompressedOffset + (j * 3 * sizeof(ushort)), 3 * sizeof(ushort));
pixel.Clear();
}
decompressedOffset += destinationPitch * sizeof(ushort);
}
decompressedBlock.Clear();
return;
}
@@ -535,7 +541,7 @@ internal static class BcHelpers
BinaryPrimitives.WriteUInt16LittleEndian(decompressedBlock.Slice(bOffset), bFinal);
}
decompressedOffset += destinationPitch * sizeof(ushort);
decompressedOffset += 4 * 3 * sizeof(ushort);
}
static uint ReadBc6hModeBits(ref BitStream bstream)
@@ -553,30 +559,20 @@ internal static class BcHelpers
}
}
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc7.DecodedBlockSize);
BitStream bstream = new(compressedBlock);
Span2D<uint> endpoints = new(stackalloc uint[6 * 4], 6, 4);
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
int decompressedOffset = 0;
int mode = ReadBc7Mode(ref bstream);
// unexpected mode, clear the block (transparent black)
if (mode >= 8)
{
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
decompressedBlock[decompressedOffset + (j * 4) + 0] = 0;
decompressedBlock[decompressedOffset + (j * 4) + 1] = 0;
decompressedBlock[decompressedOffset + (j * 4) + 2] = 0;
decompressedBlock[decompressedOffset + (j * 4) + 3] = 0;
}
decompressedOffset += destinationPitch;
}
decompressedBlock.Clear();
return;
}
@@ -814,13 +810,12 @@ internal static class BcHelpers
break;
}
decompressedBlock[decompressedOffset + (j * 4) + 0] = (byte)r;
decompressedBlock[decompressedOffset + (j * 4) + 1] = (byte)g;
decompressedBlock[decompressedOffset + (j * 4) + 2] = (byte)b;
decompressedBlock[decompressedOffset + (j * 4) + 3] = (byte)a;
int offset = (i * 16) + (j * 4);
decompressedBlock[offset + 0] = (byte)r;
decompressedBlock[offset + 1] = (byte)g;
decompressedBlock[offset + 2] = (byte)b;
decompressedBlock[offset + 3] = (byte)a;
}
decompressedOffset += destinationPitch;
}
static int ReadBc7Mode(ref BitStream bstream)
@@ -1096,4 +1091,26 @@ internal static class BcHelpers
{
(a, b) = (b, a);
}
/// <summary>
/// This is for copying a decoded block to the output buffer. It handles the pitch and non-4x4 blocks (if necessary).
/// </summary>
public static void CopyBufferToOutput<TBufferColor, TBufferChannelValue, TOutputColor, TOutputChannelValue>(Span<TBufferColor> buffer, Span<TOutputColor> output, int width, int height, int w, int h, int blockWidth, int blockHeight)
where TBufferChannelValue : unmanaged
where TBufferColor : unmanaged, IColor<TBufferChannelValue>
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
int hEnd = int.Min(h + blockHeight, height);
int wEnd = int.Min(w + blockWidth, width);
for (int i = h; i < hEnd; i++)
{
int outputOffset = i * width;
int bufferOffset = (i - h) * blockWidth;
for (int j = w; j < wEnd; j++)
{
output[outputOffset + j] = buffer[bufferOffset + (j - w)].Convert<TBufferColor, TBufferChannelValue, TOutputColor, TOutputChannelValue>();
}
}
}
}
+3 -78
View File
@@ -6,32 +6,6 @@ namespace AssetRipper.TextureDecoder.Dxt
{
public static class DxtDecoder
{
/// <summary>
/// Decompress a DXT1 image
/// </summary>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer of size 4 * width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT1(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
return DecompressDXT1<ColorBGRA32, byte>(input, width, height, out output);
}
/// <summary>
/// Decompress a DXT1 image
/// </summary>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least 4 * width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT1(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
return DecompressDXT1<ColorBGRA32, byte>(input, width, height, output);
}
/// <summary>
/// Decompress a DXT1 image
/// </summary>
@@ -64,6 +38,7 @@ namespace AssetRipper.TextureDecoder.Dxt
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT1<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
@@ -127,32 +102,6 @@ namespace AssetRipper.TextureDecoder.Dxt
return offset;
}
/// <summary>
/// Decompress a DXT3 image
/// </summary>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer of size 4 * width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT3(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
return DecompressDXT3<ColorBGRA32, byte>(input, width, height, out output);
}
/// <summary>
/// Decompress a DXT3 image
/// </summary>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least 4 * width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT3(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
return DecompressDXT3<ColorBGRA32, byte>(input, width, height, output);
}
/// <summary>
/// Decompress a DXT3 image
/// </summary>
@@ -185,6 +134,7 @@ namespace AssetRipper.TextureDecoder.Dxt
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT3<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
@@ -258,32 +208,6 @@ namespace AssetRipper.TextureDecoder.Dxt
return offset;
}
/// <summary>
/// Decompress a DXT5 image
/// </summary>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer of size 4 * width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT5(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
return DecompressDXT5<ColorBGRA32, byte>(input, width, height, out output);
}
/// <summary>
/// Decompress a DXT5 image
/// </summary>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least 4 * width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT5(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
return DecompressDXT5<ColorBGRA32, byte>(input, width, height, output);
}
/// <summary>
/// Decompress a DXT5 image
/// </summary>
@@ -316,6 +240,7 @@ namespace AssetRipper.TextureDecoder.Dxt
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT5<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
+226 -126
View File
@@ -1,15 +1,31 @@
using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Etc
{
public static class EtcDecoder
{
public static int DecompressETC(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int DecompressETC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * 4];
return DecompressETC(input, width, height, output);
return DecompressETC<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int DecompressETC(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int DecompressETC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressETC<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int DecompressETC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -19,22 +35,22 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16];
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
int inputOffset = 0;
for (int t = 0; t < bch; t++)
{
for (int s = 0; s < bcw; s++, inputOffset += 8)
{
DecodeEtc1Block(input.Slice(inputOffset, 8), buf);
DecodeEtc1Block(input.Slice(inputOffset, 8), buffer);
int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
int outputOffset = t * 4 * width + s * 4;
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{
for (int j = 0; j < clen; j++)
{
outputPtr[j + i * width] = buf[j + 4 * i];
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
}
}
}
@@ -42,14 +58,27 @@ namespace AssetRipper.TextureDecoder.Etc
return inputOffset;
}
public static int DecompressETC2(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int DecompressETC2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * 4];
return DecompressETC2(input, width, height, output);
return DecompressETC2<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int DecompressETC2(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int DecompressETC2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressETC2<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int DecompressETC2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -59,22 +88,22 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16];
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
int inputOffset = 0;
for (int t = 0; t < bch; t++)
{
for (int s = 0; s < bcw; s++, inputOffset += 8)
{
DecodeEtc2Block(input.Slice(inputOffset, 8), buf);
DecodeEtc2Block(input.Slice(inputOffset, 8), buffer);
int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
int outputOffset = t * 4 * width + s * 4;
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{
for (int j = 0; j < clen; j++)
{
outputPtr[j + i * width] = buf[j + 4 * i];
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
}
}
}
@@ -82,14 +111,27 @@ namespace AssetRipper.TextureDecoder.Etc
return inputOffset;
}
public static int DecompressETC2A1(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int DecompressETC2A1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * 4];
return DecompressETC2A1(input, width, height, output);
return DecompressETC2A1<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int DecompressETC2A1(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int DecompressETC2A1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressETC2A1<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int DecompressETC2A1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -99,22 +141,22 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16];
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
int inputOffset = 0;
for (int t = 0; t < bch; t++)
{
for (int s = 0; s < bcw; s++, inputOffset += 8)
{
DecodeEtc2a1Block(input.Slice(inputOffset, 8), buf);
DecodeEtc2a1Block(input.Slice(inputOffset, 8), buffer);
int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
int outputOffset = t * 4 * width + s * 4;
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{
for (int j = 0; j < clen; j++)
{
outputPtr[j + i * width] = buf[j + 4 * i];
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
}
}
}
@@ -122,14 +164,27 @@ namespace AssetRipper.TextureDecoder.Etc
return inputOffset;
}
public static int DecompressETC2A8(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int DecompressETC2A8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * 4];
return DecompressETC2A8(input, width, height, output);
return DecompressETC2A8<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int DecompressETC2A8(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int DecompressETC2A8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressETC2A8<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int DecompressETC2A8<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
if (input.Length < requiredLength)
{
@@ -139,23 +194,23 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16];
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
int inputOffset = 0;
for (int t = 0; t < bch; t++)
{
for (int s = 0; s < bcw; s++, inputOffset += 16)
{
DecodeEtc2Block(input.Slice(inputOffset + 8, 8), buf);
DecodeEtc2a8Block(input.Slice(inputOffset + 0, 8), buf);
DecodeEtc2Block(input.Slice(inputOffset + 8, 8), buffer);
DecodeEtc2a8Block(input.Slice(inputOffset + 0, 8), buffer);
int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
int outputOffset = t * 4 * width + s * 4;
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{
for (int j = 0; j < clen; j++)
{
outputPtr[j + i * width] = buf[j + 4 * i];
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
}
}
}
@@ -163,14 +218,27 @@ namespace AssetRipper.TextureDecoder.Etc
return inputOffset;
}
public static int DecompressEACRUnsigned(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * 4];
return DecompressEACRUnsigned(input, width, height, output);
return DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int DecompressEACRUnsigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int DecompressEACRUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -180,26 +248,26 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16];
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
for (int i = 0; i < 16; i++)
{
buf[i] = 0xFF000000;
buffer[i] = ColorRGBA<byte>.Black;
}
int inputOffset = 0;
for (int t = 0; t < bch; t++)
{
for (int s = 0; s < bcw; s++, inputOffset += 8)
{
DecodeEacUnsignedBlock(input.Slice(inputOffset, 8), buf, 2);
DecodeEacUnsignedBlock(input.Slice(inputOffset, 8), buffer, 0);
int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
int outputOffset = t * 4 * width + s * 4;
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{
for (int j = 0; j < clen; j++)
{
outputPtr[j + i * width] = buf[j + 4 * i];
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
}
}
}
@@ -207,14 +275,27 @@ namespace AssetRipper.TextureDecoder.Etc
return inputOffset;
}
public static int DecompressEACRSigned(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int DecompressEACRSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * 4];
return DecompressEACRSigned(input, width, height, output);
return DecompressEACRSigned<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int DecompressEACRSigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int DecompressEACRSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressEACRSigned<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int DecompressEACRSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -224,26 +305,26 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16];
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
for (int i = 0; i < 16; i++)
{
buf[i] = 0xFF000000;
buffer[i] = ColorRGBA<byte>.Black;
}
int inputOffset = 0;
for (int t = 0; t < bch; t++)
{
for (int s = 0; s < bcw; s++, inputOffset += 8)
{
DecodeEacSignedBlock(input.Slice(inputOffset, 8), buf, 2);
DecodeEacSignedBlock(input.Slice(inputOffset, 8), buffer, 0);
int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
int outputOffset = t * 4 * width + s * 4;
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{
for (int j = 0; j < clen; j++)
{
outputPtr[j + i * width] = buf[j + 4 * i];
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
}
}
}
@@ -251,14 +332,27 @@ namespace AssetRipper.TextureDecoder.Etc
return inputOffset;
}
public static int DecompressEACRGUnsigned(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * 4];
return DecompressEACRGUnsigned(input, width, height, output);
return DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int DecompressEACRGUnsigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int DecompressEACRGUnsigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
@@ -268,27 +362,27 @@ namespace AssetRipper.TextureDecoder.Etc
}
int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16];
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
for (int i = 0; i < 16; i++)
{
buf[i] = 0xFF000000;
buffer[i] = ColorRGBA<byte>.Black;
}
int inputOffset = 0;
for (int t = 0; t < bch; t++)
{
for (int s = 0; s < bcw; s++, inputOffset += 16)
{
DecodeEacUnsignedBlock(input.Slice(inputOffset + 0, 8), buf, 2);
DecodeEacUnsignedBlock(input.Slice(inputOffset + 8, 8), buf, 1);
DecodeEacUnsignedBlock(input.Slice(inputOffset + 0, 8), buffer, 0);
DecodeEacUnsignedBlock(input.Slice(inputOffset + 8, 8), buffer, 1);
int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
int outputOffset = t * 4 * width + s * 4;
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{
for (int j = 0; j < clen; j++)
{
outputPtr[j + i * width] = buf[j + 4 * i];
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
}
}
}
@@ -296,14 +390,27 @@ namespace AssetRipper.TextureDecoder.Etc
return inputOffset;
}
public static int DecompressEACRGSigned(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
public static int DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * 4];
return DecompressEACRGSigned(input, width, height, output);
return DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
public static int DecompressEACRGSigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
public static int DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
public static int DecompressEACRGSigned<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
if (input.Length < requiredLength)
{
@@ -313,27 +420,27 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16];
Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
for (int i = 0; i < 16; i++)
{
buf[i] = 0xFF000000;
buffer[i] = ColorRGBA<byte>.Black;
}
int inputOffset = 0;
for (int t = 0; t < bch; t++)
{
for (int s = 0; s < bcw; s++, inputOffset += 16)
{
DecodeEacSignedBlock(input.Slice(inputOffset + 0, 8), buf, 2);
DecodeEacSignedBlock(input.Slice(inputOffset + 8, 8), buf, 1);
DecodeEacSignedBlock(input.Slice(inputOffset + 0, 8), buffer, 0);
DecodeEacSignedBlock(input.Slice(inputOffset + 8, 8), buffer, 1);
int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset));
int outputOffset = t * 4 * width + s * 4;
Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{
for (int j = 0; j < clen; j++)
{
outputPtr[j + i * width] = buf[j + 4 * i];
outputPtr[j + i * width] = buffer[j + 4 * i].Convert<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>();
}
}
}
@@ -344,7 +451,7 @@ namespace AssetRipper.TextureDecoder.Etc
/// <summary>
/// Parses an 8-byte Etc1 block from <paramref name="input"/>.
/// </summary>
private static void DecodeEtc1Block(ReadOnlySpan<byte> input, Span<uint> output)
private static void DecodeEtc1Block(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
{
byte i3 = input[3];
ReadOnlySpan<int> code =
@@ -393,13 +500,12 @@ namespace AssetRipper.TextureDecoder.Etc
int index = k << 1 & 2 | j & 1;
int cd = code[s];
int m = Etc1ModifierTable[cd + index];
uint color = ApplicateColor(c, s, m);
int oi = WriteOrderTable[i];
output[oi] = color;
output[oi] = ApplicateColor(c, s, m);
}
}
private static void DecodeEtc2Block(ReadOnlySpan<byte> input, Span<uint> output)
private static void DecodeEtc2Block(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
{
int j = input[6] << 8 | input[7];
int k = input[4] << 8 | input[5];
@@ -423,7 +529,7 @@ namespace AssetRipper.TextureDecoder.Etc
}
int ti = input[3] >> 1 & 6 | input[3] & 1;
byte d = Etc2DistanceTable[ti];
ReadOnlySpan<uint> color_set =
ReadOnlySpan<ColorRGBA<byte>> color_set =
[
ApplicateColorRaw(c, 0),
ApplicateColor(c, 1, d),
@@ -434,9 +540,8 @@ namespace AssetRipper.TextureDecoder.Etc
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{
int index = k << 1 & 2 | j & 1;
uint color = color_set[index];
int oi = WriteOrderTable[i];
output[oi] = color;
output[oi] = color_set[index];
}
}
else
@@ -464,7 +569,7 @@ namespace AssetRipper.TextureDecoder.Etc
++di;
}
byte d = Etc2DistanceTable[di];
ReadOnlySpan<uint> color_set =
ReadOnlySpan<ColorRGBA<byte>> color_set =
[
ApplicateColor(c, 0, d),
ApplicateColor(c, 0, -d),
@@ -474,9 +579,8 @@ namespace AssetRipper.TextureDecoder.Etc
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{
int index = k << 1 & 2 | j & 1;
uint color = color_set[index];
int oi = WriteOrderTable[i];
output[oi] = color;
output[oi] = color_set[index];
}
}
else
@@ -507,7 +611,7 @@ namespace AssetRipper.TextureDecoder.Etc
byte ri = Clamp255((x * (c[1 * 3 + 0] - c[0 * 3 + 0]) + y * (c[2 * 3 + 0] - c[0 * 3 + 0]) + 4 * c[0 * 3 + 0] + 2) >> 2);
byte gi = Clamp255((x * (c[1 * 3 + 1] - c[0 * 3 + 1]) + y * (c[2 * 3 + 1] - c[0 * 3 + 1]) + 4 * c[0 * 3 + 1] + 2) >> 2);
byte bi = Clamp255((x * (c[1 * 3 + 2] - c[0 * 3 + 2]) + y * (c[2 * 3 + 2] - c[0 * 3 + 2]) + 4 * c[0 * 3 + 2] + 2) >> 2);
output[i] = Color(ri, gi, bi, 255);
output[i] = new(ri, gi, bi, byte.MaxValue);
}
}
}
@@ -538,9 +642,8 @@ namespace AssetRipper.TextureDecoder.Etc
int index = k << 1 & 2 | j & 1;
int ci = code[s];
int m = Etc1ModifierTable[ci + index];
uint color = ApplicateColor(c, s, m);
int oi = WriteOrderTable[i];
output[oi] = color;
output[oi] = ApplicateColor(c, s, m);
}
}
}
@@ -570,15 +673,14 @@ namespace AssetRipper.TextureDecoder.Etc
int index = k << 1 & 2 | j & 1;
int ci = code[s];
int m = Etc1ModifierTable[ci + index];
uint color = ApplicateColor(c, s, m);
int oi = WriteOrderTable[i];
output[oi] = color;
output[oi] = ApplicateColor(c, s, m);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void DecodeEtc2a1Block(ReadOnlySpan<byte> input, Span<uint> output)
private static void DecodeEtc2a1Block(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
{
if ((input[3] & 2) != 0)
{
@@ -591,7 +693,7 @@ namespace AssetRipper.TextureDecoder.Etc
}
}
private static void DecodeEtc2PunchThrowBlock(ReadOnlySpan<byte> input, Span<uint> output)
private static void DecodeEtc2PunchThrowBlock(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
{
int j = input[6] << 8 | input[7];
int k = input[4] << 8 | input[5];
@@ -612,7 +714,7 @@ namespace AssetRipper.TextureDecoder.Etc
}
int ti = input[3] >> 1 & 6 | input[3] & 1;
byte d = Etc2DistanceTable[ti];
ReadOnlySpan<uint> color_set =
ReadOnlySpan<ColorRGBA<byte>> color_set =
[
ApplicateColorRaw(c, 0),
ApplicateColor(c, 1, d),
@@ -623,10 +725,8 @@ namespace AssetRipper.TextureDecoder.Etc
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{
int index = k << 1 & 2 | j & 1;
uint color = color_set[index];
uint mask = PunchthroughMaskTable[index];
int oi = WriteOrderTable[i];
output[oi] = color & mask;
output[oi] = PunchthroughBooleanTable[index] ? color_set[index] : default;
}
}
else
@@ -654,7 +754,7 @@ namespace AssetRipper.TextureDecoder.Etc
++di;
}
byte d = Etc2DistanceTable[di];
ReadOnlySpan<uint> color_set =
ReadOnlySpan<ColorRGBA<byte>> color_set =
[
ApplicateColor(c, 0, d),
ApplicateColor(c, 0, -d),
@@ -664,10 +764,8 @@ namespace AssetRipper.TextureDecoder.Etc
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{
int index = k << 1 & 2 | j & 1;
uint color = color_set[index];
uint mask = PunchthroughMaskTable[index];
int oi = WriteOrderTable[i];
output[oi] = color & mask;
output[oi] = PunchthroughBooleanTable[index] ? color_set[index] : default;
}
}
else
@@ -698,7 +796,7 @@ namespace AssetRipper.TextureDecoder.Etc
byte ri = Clamp255((x * (c[1 * 3 + 0] - c[0 * 3 + 0]) + y * (c[2 * 3 + 0] - c[0 * 3 + 0]) + 4 * c[0 * 3 + 0] + 2) >> 2);
byte gi = Clamp255((x * (c[1 * 3 + 1] - c[0 * 3 + 1]) + y * (c[2 * 3 + 1] - c[0 * 3 + 1]) + 4 * c[0 * 3 + 1] + 2) >> 2);
byte bi = Clamp255((x * (c[1 * 3 + 2] - c[0 * 3 + 2]) + y * (c[2 * 3 + 2] - c[0 * 3 + 2]) + 4 * c[0 * 3 + 2] + 2) >> 2);
output[i] = Color(ri, gi, bi, 255);
output[i] = new(ri, gi, bi, byte.MaxValue);
}
}
}
@@ -725,22 +823,28 @@ namespace AssetRipper.TextureDecoder.Etc
}
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{
int s = Etc1SubblockTable[ti + i];
int index = k << 1 & 2 | j & 1;
int ci = code[s];
int m = PunchthroughModifierTable[ci + index];
uint color = ApplicateColor(c, s, m);
uint mask = PunchthroughMaskTable[index];
int oi = WriteOrderTable[i];
output[oi] = color & mask;
if (PunchthroughBooleanTable[index])
{
int s = Etc1SubblockTable[ti + i];
int ci = code[s];
int m = PunchthroughModifierTable[ci + index];
output[oi] = ApplicateColor(c, s, m);
}
else
{
output[oi] = default;
}
}
}
}
}
}
private static void DecodeEtc2a8Block(ReadOnlySpan<byte> input, Span<uint> output)
private static void DecodeEtc2a8Block(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int @base = input[0];
int data1 = input[1];
int mul = data1 >> 4;
@@ -748,48 +852,50 @@ namespace AssetRipper.TextureDecoder.Etc
{
for (int i = 0; i < 16; i++)
{
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(3), @base);
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(3), @base);
}
}
else
{
int table = data1 & 0xF;
ulong l = Get6SwapedBytes(input);
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(3), @base, table, mul, l);
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(3), @base, table, mul, l);
}
}
private static void DecodeEacUnsignedBlock(ReadOnlySpan<byte> input, Span<uint> output, int channel)
private static void DecodeEacUnsignedBlock(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output, int channel)
{
ThrowHelper.ThrowIfNotLittleEndian();
int @base = input[0];
int data1 = input[1];
int mul = data1 >> 4;
if (mul == 0)
{
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base);
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base);
}
else
{
int table = data1 & 0xF;
ulong l = Get6SwapedBytes(input);
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base, table, mul, l);
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base, table, mul, l);
}
}
private static void DecodeEacSignedBlock(ReadOnlySpan<byte> input, Span<uint> output, int channel)
private static void DecodeEacSignedBlock(ReadOnlySpan<byte> input, Span<ColorRGBA<byte>> output, int channel)
{
ThrowHelper.ThrowIfNotLittleEndian();
int @base = 127 + unchecked((sbyte)input[0]);
int data1 = input[1];
int mul = data1 >> 4;
if (mul == 0)
{
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base);
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base);
}
else
{
int table = data1 & 0xF;
ulong l = Get6SwapedBytes(input);
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base, table, mul, l);
DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base, table, mul, l);
}
}
@@ -813,12 +919,6 @@ namespace AssetRipper.TextureDecoder.Etc
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static uint Color(int r, int g, int b, int a)
{
return unchecked((uint)(r << 16 | g << 8 | b | a << 24));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static byte Clamp255(int n)
{
@@ -826,21 +926,21 @@ namespace AssetRipper.TextureDecoder.Etc
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static uint ApplicateColor(ReadOnlySpan<byte> c, int o, int m)
private static ColorRGBA<byte> ApplicateColor(ReadOnlySpan<byte> c, int o, int m)
{
return Color(Clamp255(c[o * 3 + 0] + m), Clamp255(c[o * 3 + 1] + m), Clamp255(c[o * 3 + 2] + m), 255);
return new(Clamp255(c[o * 3 + 0] + m), Clamp255(c[o * 3 + 1] + m), Clamp255(c[o * 3 + 2] + m), byte.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static uint ApplicateColor(ReadOnlySpan<int> c, int o, int m)
private static ColorRGBA<byte> ApplicateColor(ReadOnlySpan<int> c, int o, int m)
{
return Color(Clamp255(c[o * 3 + 0] + m), Clamp255(c[o * 3 + 1] + m), Clamp255(c[o * 3 + 2] + m), 255);
return new(Clamp255(c[o * 3 + 0] + m), Clamp255(c[o * 3 + 1] + m), Clamp255(c[o * 3 + 2] + m), byte.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static uint ApplicateColorRaw(ReadOnlySpan<byte> c, int o)
private static ColorRGBA<byte> ApplicateColorRaw(ReadOnlySpan<byte> c, int o)
{
return Color(c[o * 3 + 0], c[o * 3 + 1], c[o * 3 + 2], 255);
return new(c[o * 3 + 0], c[o * 3 + 1], c[o * 3 + 2], byte.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -898,6 +998,6 @@ namespace AssetRipper.TextureDecoder.Etc
-4, -6, -8, -9, 3, 5, 7, 8,
-3, -5, -7, -9, 2, 4, 6, 8,
];
private static ReadOnlySpan<uint> PunchthroughMaskTable => [0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF];
private static ReadOnlySpan<bool> PunchthroughBooleanTable => [true, true, false, true];
}
}
@@ -1,38 +1,65 @@
//#define DISABLE_TWINDDLING_ROUTINE
#define ASSUME_IMAGE_TILING
using AssetRipper.TextureDecoder.Rgb;
using System.Diagnostics;
namespace AssetRipper.TextureDecoder.Pvrtc
{
public static partial class PvrtcDecoder
{
/// <summary>
/// Decompresses PVRTC to RGBA 8888
/// Decompresses PVRTC
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">The PVRTC texture data to decompress</param>
/// <param name="xDim">X dimension (width) of the texture</param>
/// <param name="yDim">Y dimension (height) of the texture</param>
/// <param name="output">The decompressed texture data</param>
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
/// <returns>The number of bytes read from <paramref name="input"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int DecompressPVRTC(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, out byte[] output)
public static int DecompressPVRTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[xDim * yDim * sizeof(uint)];
return DecompressPVRTC(input, xDim, yDim, do2bitMode, output);
output = new byte[xDim * yDim * Unsafe.SizeOf<TOutputColor>()];
return DecompressPVRTC<TOutputColor, TOutputChannelValue>(input, xDim, yDim, do2bitMode, output);
}
/// <summary>
/// Decompresses PVRTC to RGBA 8888
/// Decompresses PVRTC
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">The PVRTC texture data to decompress</param>
/// <param name="xDim">X dimension (width) of the texture</param>
/// <param name="yDim">Y dimension (height) of the texture</param>
/// <param name="output">The decompressed texture data</param>
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
/// <returns>The number of bytes read from <paramref name="input"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int DecompressPVRTC(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<byte> output)
public static int DecompressPVRTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressPVRTC<TOutputColor, TOutputChannelValue>(input, xDim, yDim, do2bitMode, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
/// <summary>
/// Decompresses PVRTC
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">The PVRTC texture data to decompress</param>
/// <param name="xDim">X dimension (width) of the texture</param>
/// <param name="yDim">Y dimension (height) of the texture</param>
/// <param name="output">The decompressed texture data</param>
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
/// <returns>The number of bytes read from <paramref name="input"/></returns>
public static int DecompressPVRTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, xDim * yDim);
int xBlockSize = do2bitMode ? BlockX2bpp : BlockX4bpp;
// for MBX don't allow the sizes to get too small
int blockXDim = Math.Max(2, xDim / xBlockSize);
@@ -65,12 +92,12 @@ namespace AssetRipper.TextureDecoder.Pvrtc
int blockX = (x - xBlockSize / 2);
int blockY = (y - BlockYSize / 2);
blockX = LimitCoord(blockX, xDim) / xBlockSize;
blockY = LimitCoord(blockY, yDim) / BlockYSize;
blockX = LimitCoordinate(blockX, xDim) / xBlockSize;
blockY = LimitCoordinate(blockY, yDim) / BlockYSize;
// compute the positions of the other 3 blocks
int blockXp1 = LimitCoord(blockX + 1, blockXDim);
int blockYp1 = LimitCoord(blockY + 1, blockYDim);
int blockXp1 = LimitCoordinate(blockX + 1, blockXDim);
int blockYp1 = LimitCoordinate(blockY + 1, blockYDim);
// map to block memory locations
uint blocki00 = TwiddleUV((uint)blockYDim, (uint)blockXDim, (uint)blockY, (uint)blockX);
@@ -108,12 +135,12 @@ namespace AssetRipper.TextureDecoder.Pvrtc
InterpolateColours(m_colors[0], m_colors[1], m_colors[2], m_colors[3], 1, do2bitMode, x, y, bSig);
GetModulationValue(x, y, do2bitMode, modulationVals, modulationModes, out int mod, out bool doPT);
// compute the modulated color. Swap red and blue channel
int position = (x + y * xDim) << 2;
output[position + 0] = (byte)((aSig[2] * 8 + mod * (bSig[2] - aSig[2])) >> 3);
output[position + 1] = (byte)((aSig[1] * 8 + mod * (bSig[1] - aSig[1])) >> 3);
output[position + 2] = (byte)((aSig[0] * 8 + mod * (bSig[0] - aSig[0])) >> 3);
output[position + 3] = doPT ? (byte)0 : (byte)((aSig[3] * 8 + mod * (bSig[3] - aSig[3])) >> 3);
// compute the modulated color.
byte red = (byte)((aSig[0] * 8 + mod * (bSig[0] - aSig[0])) >> 3);
byte green = (byte)((aSig[1] * 8 + mod * (bSig[1] - aSig[1])) >> 3);
byte blue = (byte)((aSig[2] * 8 + mod * (bSig[2] - aSig[2])) >> 3);
byte alpha = doPT ? (byte)0 : (byte)((aSig[3] * 8 + mod * (bSig[3] - aSig[3])) >> 3);
output[x + y * xDim].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>(red, green, blue, alpha);
}
}
@@ -132,24 +159,10 @@ namespace AssetRipper.TextureDecoder.Pvrtc
/// <returns>The twiddled offset of the pixel</returns>
private static uint TwiddleUV(uint ySize, uint xSize, uint yPos, uint xPos)
{
#if DEBUG
if (yPos >= ySize)
{
throw new Exception();
}
if (xPos >= xSize)
{
throw new Exception();
}
if (!BitOperations.IsPow2(ySize))
{
throw new Exception();
}
if (!BitOperations.IsPow2(xSize))
{
throw new Exception();
}
#endif
Debug.Assert(yPos < ySize);
Debug.Assert(xPos < xSize);
Debug.Assert(BitOperations.IsPow2(ySize));
Debug.Assert(BitOperations.IsPow2(xSize));
uint minDimension;
uint maxValue;
@@ -164,10 +177,6 @@ namespace AssetRipper.TextureDecoder.Pvrtc
maxValue = yPos;
}
#if DISABLE_TWINDDLING_ROUTINE
// nasty hack to disable twiddling
return (yPos * xSize + xPos);
#else
// step through all the bits in the "minimum" dimension
uint srcBitPos = 1;
uint dstBitPos = 1;
@@ -195,7 +204,6 @@ namespace AssetRipper.TextureDecoder.Pvrtc
maxValue >>= shiftCount;
twiddled |= (maxValue << (2 * shiftCount));
return twiddled;
#endif
}
/// <summary>
@@ -290,16 +298,11 @@ namespace AssetRipper.TextureDecoder.Pvrtc
}
}
#if DEBUG
// sanity check
for (int i = 0; i < 4; i++)
{
if (result[i] >= 256)
{
throw new Exception("Sanity failed");
}
}
#endif
Debug.Assert(result[0] < 256);
Debug.Assert(result[1] < 256);
Debug.Assert(result[2] < 256);
Debug.Assert(result[3] < 256);
// convert from 5554 to 8888, do RGB 5.3 => 8
for (int i = 0; i < 3; i++)
@@ -308,16 +311,11 @@ namespace AssetRipper.TextureDecoder.Pvrtc
}
result[3] += result[3] >> 4;
#if DEBUG
// 2nd sanity check
for (int i = 0; i < 4; i++)
{
if (result[i] >= 256)
{
throw new Exception("2nd sanity failed");
}
}
#endif
Debug.Assert(result[0] < 256);
Debug.Assert(result[1] < 256);
Debug.Assert(result[2] < 256);
Debug.Assert(result[3] < 256);
}
/// <summary>
@@ -456,19 +454,20 @@ namespace AssetRipper.TextureDecoder.Pvrtc
}
/// <summary>
/// Define an expression to either wrap or clamp large or small vals to the legal coordinate range
/// Define an expression to either wrap or clamp large or small values to the legal coordinate range.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static int LimitCoord(int value, int size)
private static int LimitCoordinate(int value, int size, bool assumeImageTiling = true)
{
#if ASSUME_IMAGE_TILING
// wrap coord
return value & (size - 1);
#else
// clamp
return h < x ? h : (x > l ? x : l);
return Clamp(Val, 0, Size - 1);
#endif
if (assumeImageTiling)
{
// wrap coordinate
return value & (size - 1);
}
else
{
// clamp coordinate
return int.Clamp(value, 0, size - 1);
}
}
/// <summary>
+28 -2
View File
@@ -503,6 +503,7 @@ namespace AssetRipper.TextureDecoder.Rgb
where TDestinationChannel : unmanaged
where TDestinationColor : unmanaged, IColor<TDestinationChannel>
{
ThrowHelper.ThrowIfNotLittleEndian();
ReadOnlySpan<TSourceColor> sourceSpan = MemoryMarshal.Cast<byte, TSourceColor>(input).Slice(0, width * height);
Span<TDestinationColor> destinationSpan = MemoryMarshal.Cast<byte, TDestinationColor>(output).Slice(0, width * height);
Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>(sourceSpan, destinationSpan);
@@ -516,9 +517,34 @@ namespace AssetRipper.TextureDecoder.Rgb
where TDestinationChannel : unmanaged
where TDestinationColor : unmanaged, IColor<TDestinationChannel>
{
for (int i = 0; i < sourceSpan.Length; i++)
if (sourceSpan.Length is 0)
{
destinationSpan[i] = sourceSpan[i].Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>();
// Do nothing if the source span is empty
}
else if (destinationSpan.Length < sourceSpan.Length)
{
ThrowDestinationSpanNotLargeEnough();
}
else if (typeof(TSourceColor) != typeof(TDestinationColor))
{
for (int i = 0; i < sourceSpan.Length; i++)
{
destinationSpan[i] = sourceSpan[i].Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>();
}
}
else if (Unsafe.AreSame(in sourceSpan[0], ref Unsafe.As<TDestinationColor, TSourceColor>(ref destinationSpan[0])))
{
// Do nothing because the source and destination point to the same memory
}
else
{
// Note: This assumes that the source and destination will never overlap.
sourceSpan.CopyTo(MemoryMarshal.Cast<TDestinationColor, TSourceColor>(destinationSpan));
}
static void ThrowDestinationSpanNotLargeEnough()
{
throw new ArgumentException("Destination span is not large enough to hold the converted data.", nameof(destinationSpan));
}
}
@@ -19,5 +19,13 @@
throw new InvalidOperationException($"Not enough space. {spaceRequired} is required, but only {spaceAvailable} is available");
}
}
internal static void ThrowIfNotLittleEndian()
{
if (!BitConverter.IsLittleEndian)
{
throw new PlatformNotSupportedException();
}
}
}
}
+1 -27
View File
@@ -1,36 +1,9 @@
using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Yuy2
{
public static class Yuy2Decoder
{
/// <summary>
/// Decompress a YUY2 image
/// </summary>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer of size 4 * width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressYUY2(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
return DecompressYUY2<ColorBGRA32, byte>(input, width, height, out output);
}
/// <summary>
/// Decompress a YUY2 image
/// </summary>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least 4 * width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressYUY2(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
return DecompressYUY2<ColorBGRA32, byte>(input, width, height, output);
}
/// <summary>
/// Decompress a YUY2 image
/// </summary>
@@ -63,6 +36,7 @@ namespace AssetRipper.TextureDecoder.Yuy2
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressYUY2<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
+2 -2
View File
@@ -1,12 +1,12 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<Version>2.2.1</Version>
<Version>2.4.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<Copyright>Copyright © ds5678</Copyright>
<Authors>ds5678</Authors>