Compare commits

...
69 Commits
Author SHA1 Message Date
ds5678 13b4f6ee49 Bump version to 2.6.3 in Directory.Build.props
Updated VersionPrefix from 2.6.2 to 2.6.3 to release a new project version.
2026-08-23 13:50:59 -07:00
ds5678 2cdd532c31 Update NuGet packages and remove coverlet.collector
Upgraded Microsoft.NET.Test.Sdk, NUnit, NUnit3TestAdapter, and NUnit.Analyzers in AssetRipper.TextureDecoder.Tests.csproj. Removed coverlet.collector package reference. Updated Microsoft.SourceLink.GitHub from 10.0.103 to 10.0.400 in AssetRipper.TextureDecoder.csproj.
2026-08-23 13:46:51 -07:00
dependabot[bot] d772c2790d Bump actions/checkout from 6 to 7 (#114)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [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/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  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>
2026-08-16 18:28:04 -07:00
dependabot[bot] 0201123b0a Bump actions/setup-dotnet from 5.3.0 to 6.0.0 (#113)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.3.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.3.0...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 6.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>
2026-08-16 18:27:51 -07:00
Jeremy Pritts 02531ab941 Ensure documentation file is named correctly 2026-08-15 21:15:42 -07:00
dependabot[bot] 2f8d0b4a8b Bump actions/setup-dotnet from 5.1.0 to 5.3.0 (#111)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.1.0 to 5.3.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.1.0...v5.3.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 5.3.0
  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>
2026-08-13 19:44:41 -07:00
砕形怨憎 0fb5e24d81 Fix chlist attribute size and scan line offset table (#112)
OpenEXRFileLayout defines a chlist attribute value as "A sequence of channels
followed by a null byte (0x00)", and the attribute size as "the size (in bytes)
of the attribute value". Size left out the terminating null byte, so a reader
that trusts the declared size lands on that byte and reads it as an empty
attribute name, which terminates the header after the first attribute.

The same document defines a regular scan line block as "y coordinate | pixel
data size | pixel data", and an offset table entry as "the distance, in bytes,
between the start of the file and the start of the chunk". The table advanced
by one int plus the pixel data, leaving out the y coordinate, so every entry
after the first pointed four bytes past the start of its chunk.

https://openexr.com/en/latest/OpenEXRFileLayout.html
2026-08-13 19:44:06 -07:00
ds5678 ec91662b4a Fix DecodeASTC method not using size of TOutputColor 2026-08-10 16:50:31 -07:00
ds5678 03713d8d57 Update version to 2.6.2 in Directory.Build.props
Bump the VersionPrefix property from 2.6.1 to 2.6.2 to prepare for the next release.
2026-06-02 14:44:26 -07:00
ds5678 3cf2170242 Ensure that blocks with all bits set do not throw 2026-06-02 14:32:22 -07:00
ds5678 0a8bae09cf Refactor: use Span.Fill for block color assignment
Replaced manual for loop with Span.Fill to efficiently assign a color value to the output span in ASTC block decoding. This improves code readability and performance.
2026-05-21 21:50:21 -07:00
ds5678 a7a12fdd72 Rename solution file 2026-03-02 16:48:41 -08:00
ds5678 801edea4cf Bump version prefix to 2.6.1
Updated Directory.Build.props to increment the VersionPrefix from 2.6.0 to 2.6.1. This prepares the project for the next release.
2026-02-26 18:14:11 -08:00
ds5678 3cb859c6ac Update NuGet package versions across project files
Updated several NuGet dependencies to their latest versions:
- AssetRipper.Text.SourceGeneration to 1.2.3
- Microsoft.SourceLink.GitHub to 10.0.103
- Microsoft.NET.Test.Sdk to 18.3.0
- NUnit to 4.5.0
- coverlet.collector to 8.0.0
These updates provide bug fixes, new features, and improved compatibility.
2026-02-26 17:55:38 -08:00
ds5678 0738ba4b50 Multi-target .NET 8 and 10
I currently don't use any API above .NET 8, so multi-targeting 8 and 10 is fine for now. However, if I later need a higher .NET version, I won't hesitate to drop support.
2026-02-26 17:49:35 -08:00
ds5678 09067cecbb Replace Unsafe.As with Unsafe.BitCast where appropriate 2026-02-26 17:39:51 -08:00
ds5678 ec6754a5d2 Replace tuple with record struct in color generation 2026-02-26 17:28:31 -08:00
ds5678 bc4d0f5378 Bump version prefix to 2.6.0
Updated the VersionPrefix property in Directory.Build.props from 2.5.0 to 2.6.0 to prepare for the next project release.
2026-01-25 14:53:25 -08:00
ds5678 8f0f94695b More accurate conversion from uint to ushort and byte 2026-01-25 14:51:56 -08:00
ds5678 aacbfbd222 More efficient constant estimation
Some accuracy is sacrificed
2026-01-25 14:34:19 -08:00
ds5678 93fe6948b6 Better identification for perfect sets of integer constants 2026-01-25 03:02:31 -08:00
ds5678 0e6f3080be Conversion between linear and sRGB 2026-01-25 00:25:41 -08:00
ds5678 f8294eff8b Move old RgbConverter methods to the test project 2026-01-25 00:16:09 -08:00
ds5678 0b4d1dfc83 Delete benchmarks project 2026-01-25 00:12:41 -08:00
ds5678 f5c1aca0e1 Improve numeric conversion involving floating point numbers
32+ bit integers <-> floating point numbers are more precise
2026-01-24 23:34:44 -08:00
ds5678 d4d335d94b Update NuGet packages and copyright year range
Upgraded BenchmarkDotNet, Microsoft.NET.Test.Sdk, NUnit3TestAdapter, and NUnit.Analyzers to latest versions in project files. Updated Microsoft.SourceLink.GitHub and extended copyright to 2026.
2026-01-24 22:42:27 -08:00
ds5678 70775daca9 Bump to .NET 10 2026-01-24 22:40:28 -08:00
dependabot[bot] 3a94ea1a0f Bump actions/checkout from 5 to 6 (#102)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [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/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  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>
2026-01-24 22:38:16 -08:00
dependabot[bot] db875125d2 Bump actions/setup-dotnet from 5.0.0 to 5.1.0 (#104)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.0.0...v5.1.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 5.1.0
  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>
2026-01-24 22:37:59 -08:00
ds5678 cdd73752df Bump version to 2.5.0 2025-11-27 12:48:04 -08:00
ds5678 99556c46dd Assert.EnterMultipleScope 2025-11-27 12:42:57 -08:00
ds5678 c919852137 Generic ColorARGB and ColorBGRA 2025-11-27 12:37:42 -08:00
ds5678 9bca952f2d Switch to .slnx solution format for improved structure
Replaced the traditional `TextureDecoder.sln` file with a new `TextureDecoder.slnx` file, which uses a hierarchical XML-based format. The new format organizes solution items and project references more cleanly, removing verbose configuration details like `SolutionConfigurationPlatforms` and `NestedProjects`. This transition simplifies the solution structure and enhances maintainability.
2025-11-27 11:59:42 -08:00
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
ds5678 19b2469595 Bump version 2024-08-03 10:25:22 -07:00
ds5678 c5a2382733 Fix bc7 mode reading regression 2024-08-02 20:56:59 -07:00
ds5678 9ce8311e1b Bc optimizations 2024-08-01 23:33:37 -07:00
ds5678 e39b439ccd Use UInt128 in BitStream 2024-08-01 23:09:20 -07:00
ds5678 f2da43bcbb BitStream optimizations 2024-08-01 22:43:08 -07:00
ds5678 128eb58864 Use unsigned integers for Bc decoding 2024-08-01 22:34:02 -07:00
dependabot[bot] 9f7a61172a Bump NUnit3TestAdapter from 4.5.0 to 4.6.0
Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases)
- [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V4.5.0...V4.6.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2024-07-25 21:26:33 -07:00
dependabot[bot] 93c177232f Bump NUnit.Analyzers from 4.1.0 to 4.2.0
Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 4.1.0 to 4.2.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.1.0...4.2.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-07-09 21:24:01 -07:00
dependabot[bot] 8b62957cdb Bump Microsoft.NET.Test.Sdk from 17.9.0 to 17.10.0
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.9.0 to 17.10.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.9.0...v17.10.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-07-09 21:23:49 -07:00
dependabot[bot] 95ecf5b1ac Bump actions/setup-dotnet from 4.0.0 to 4.0.1
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4.0.0...v4.0.1)

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

Signed-off-by: dependabot[bot] <[email protected]>
2024-07-09 21:23:32 -07:00
ds5678 f8d17ad290 Use ReadOnlySpan for constant data 2024-04-14 20:24:19 -07:00
76 changed files with 2869 additions and 2665 deletions

No files matched your search

+3 -3
View File
@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v7
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v4.0.0 uses: actions/setup-dotnet@v6.0.0
with: with:
dotnet-version: 8.0.x dotnet-version: 9.0.x
- name: Restore dependencies - name: Restore dependencies
run: dotnet restore run: dotnet restore
- name: Build - name: Build
@@ -1,25 +0,0 @@
using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Formats;
using BenchmarkDotNet.Attributes;
namespace AssetRipper.TextureDecoder.Benchmarks;
public class Argb32Decoding
{
private const int Width = 256;//ignoring smaller mips
private const int Height = 256;//ignoring smaller mips
private readonly byte[] inputData;
private readonly byte[] outputData;
public Argb32Decoding()
{
inputData = File.ReadAllBytes(PathConstants.PathToTestFiles + "Rgb/" + "test.argb32");
outputData = new byte[Width * Height * sizeof(uint)];
}
[Benchmark]
public int OriginalMethod() => RgbConverter.ARGB32ToBGRA32(inputData, Width, Height, outputData);
[Benchmark]
public int GenericMethod() => RgbConverter.Convert<ColorARGB32, byte, ColorBGRA32, byte>(inputData, Width, Height, outputData);
}
@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AssetRipper.TextureDecoder\AssetRipper.TextureDecoder.csproj" />
</ItemGroup>
</Project>
@@ -1,7 +0,0 @@
namespace AssetRipper.TextureDecoder.Benchmarks;
internal static class PathConstants
{
internal const string PathToRepositoryRoot = "../../../../../../../../";
internal const string PathToTestFiles = PathToRepositoryRoot + "TestFiles/";
}
@@ -1,11 +0,0 @@
using BenchmarkDotNet.Running;
namespace AssetRipper.TextureDecoder.Benchmarks;
internal class Program
{
static void Main()
{
BenchmarkRunner.Run(typeof(Program).Assembly);
}
}
@@ -1,25 +0,0 @@
using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Formats;
using BenchmarkDotNet.Attributes;
namespace AssetRipper.TextureDecoder.Benchmarks;
public class Rgb9e5Decoding
{
private const int Width = 256;//ignoring smaller mips
private const int Height = 256;//ignoring smaller mips
private readonly byte[] inputData;
private readonly byte[] outputData;
public Rgb9e5Decoding()
{
inputData = File.ReadAllBytes(PathConstants.PathToTestFiles + "Rgb/" + "test.rgb9e5float");
outputData = new byte[Width * Height * sizeof(uint)];
}
[Benchmark]
public int OriginalMethod() => RgbConverter.RGB9e5FloatToBGRA32(inputData, Width, Height, outputData);
[Benchmark]
public int GenericMethod() => RgbConverter.Convert<ColorRGB9e5, double, ColorBGRA32, byte>(inputData, Width, Height, outputData);
}
@@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" /> <PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -82,14 +82,14 @@ internal static class NumericConversionGenerator
writer.WriteLine("if (typeof(TFrom) == typeof(TTo))"); writer.WriteLine("if (typeof(TFrom) == typeof(TTo))");
using (new CurlyBrackets(writer)) using (new CurlyBrackets(writer))
{ {
writer.WriteLine("return Unsafe.As<TFrom, TTo>(ref value);"); writer.WriteLine("return Unsafe.BitCast<TFrom, TTo>(value);");
} }
foreach (CSharpPrimitives.Data from in CSharpPrimitives.List) foreach (CSharpPrimitives.Data from in CSharpPrimitives.List)
{ {
writer.WriteLine($"else if (typeof(TFrom) == typeof({from.LangName}))"); writer.WriteLine($"else if (typeof(TFrom) == typeof({from.LangName}))");
using (new CurlyBrackets(writer)) using (new CurlyBrackets(writer))
{ {
writer.WriteLine($"return {ConvertMethodName(from.Type)}<TTo>(Unsafe.As<TFrom, {from.LangName}>(ref value));"); writer.WriteLine($"return {ConvertMethodName(from.Type)}<TTo>(Unsafe.BitCast<TFrom, {from.LangName}>(value));");
} }
} }
writer.WriteLine("else"); writer.WriteLine("else");
@@ -133,40 +133,41 @@ internal static class NumericConversionGenerator
{ {
if (from == to) if (from == to)
{ {
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref value);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(value);");
} }
else if (to.HasPointerSize(out CSharpPrimitives.Data? bit32To, out CSharpPrimitives.Data? bit64To)) else if (to.HasPointerSize(out CSharpPrimitives.Data? bit32To, out CSharpPrimitives.Data? bit64To))
{ {
using (new If(writer, "IntPtr.Size == sizeof(int)")) using (new If(writer, "IntPtr.Size == sizeof(int)"))
{ {
writer.WriteLine($"{to.LangName} converted = ({to.LangName}){methodName}<{bit32To.LangName}>(value);"); writer.WriteLine($"{to.LangName} converted = ({to.LangName}){methodName}<{bit32To.LangName}>(value);");
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
using (new Else(writer)) using (new Else(writer))
{ {
writer.WriteLine($"{to.LangName} converted = ({to.LangName}){methodName}<{bit64To.LangName}>(value);"); writer.WriteLine($"{to.LangName} converted = ({to.LangName}){methodName}<{bit64To.LangName}>(value);");
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
} }
else if (to.IsSignedInteger(out CSharpPrimitives.Data? unsignedTo)) else if (to.IsSignedInteger(out CSharpPrimitives.Data? unsignedTo))
{ {
writer.WriteLine($"{to.LangName} converted = {ChangeSign}({methodName}<{unsignedTo.LangName}>(value));"); writer.WriteLine($"{to.LangName} converted = {ChangeSign}({methodName}<{unsignedTo.LangName}>(value));");
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
else if (from.IsFloatingPoint) else if (from.IsFloatingPoint)
{ {
if (to.IsFloatingPoint) if (to.IsFloatingPoint)
{ {
writer.WriteLine($"{to.LangName} converted = ({to.LangName})value;"); writer.WriteLine($"{to.LangName} converted = ({to.LangName})value;");
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
else else
{ {
Debug.Assert(to.IsUnsignedInteger); Debug.Assert(to.IsUnsignedInteger);
if (from.Type == typeof(Half)) CSharpPrimitives.Data conversionType = CSharpPrimitives.GetDataForIntegerFloatingPointConversion(to, from);
if (conversionType != from)
{ {
writer.WriteComment("We use float because it has enough precision to convert from Half to any integer type."); writer.WriteComment($"We use {conversionType.LangName} because it has enough precision to convert from {from.LangName} to {to.LangName}.");
writer.WriteLine($"return {ConvertMethodName(typeof(float))}<TTo>((float)value);"); writer.WriteLine($"return {ConvertMethodName(conversionType.Type)}<TTo>(({conversionType.LangName})value);");
} }
else else
{ {
@@ -175,7 +176,7 @@ internal static class NumericConversionGenerator
string maxValue = to.MaxValue; string maxValue = to.MaxValue;
writer.WriteLine($"{from.LangName} x = value * ({from.LangName}){maxValue};"); writer.WriteLine($"{from.LangName} x = value * ({from.LangName}){maxValue};");
writer.WriteLine($"{to.LangName} converted = ({from.LangName}){maxValue} < x ? {maxValue} : (x > ({from.LangName}){minValue} ? ({to.LangName})x : {minValue});"); writer.WriteLine($"{to.LangName} converted = ({from.LangName}){maxValue} < x ? {maxValue} : (x > ({from.LangName}){minValue} ? ({to.LangName})x : {minValue});");
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
} }
} }
@@ -184,17 +185,18 @@ internal static class NumericConversionGenerator
Debug.Assert(from.IsUnsignedInteger); Debug.Assert(from.IsUnsignedInteger);
if (to.IsFloatingPoint) if (to.IsFloatingPoint)
{ {
if (to.Type == typeof(Half)) CSharpPrimitives.Data conversionType = CSharpPrimitives.GetDataForIntegerFloatingPointConversion(from, to);
if (conversionType != to)
{ {
writer.WriteComment("There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first."); writer.WriteComment($"There isn't enough precision to convert from {from.LangName} to {to.LangName}, so we convert to {conversionType.LangName} first.");
writer.WriteLine($"float x = {methodName}<float>(value);"); writer.WriteLine($"{conversionType.LangName} x = {methodName}<{conversionType.LangName}>(value);");
writer.WriteLine("Half converted = (Half)x;"); writer.WriteLine($"{to.LangName} converted = ({to.LangName})x;");
writer.WriteLine("return Unsafe.As<Half, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
else else
{ {
writer.WriteLine($"{to.LangName} converted = ({to.LangName})value / ({to.LangName}){from.MaxValue};"); writer.WriteLine($"{to.LangName} converted = ({to.LangName})value / ({to.LangName}){from.MaxValue};");
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
} }
else else
@@ -214,7 +216,7 @@ internal static class NumericConversionGenerator
writer.Write($"(({conversionType})value << {i * from.Size * 8}) | "); writer.Write($"(({conversionType})value << {i * from.Size * 8}) | ");
} }
writer.WriteLine("value);"); writer.WriteLine("value);");
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
} }
else else
@@ -225,7 +227,14 @@ internal static class NumericConversionGenerator
writer.WriteComment("This is a special case where we already know an optimal algorithm."); writer.WriteComment("This is a special case where we already know an optimal algorithm.");
writer.WriteLine("uint x = (value * 255u + 32895u) >> 16;"); writer.WriteLine("uint x = (value * 255u + 32895u) >> 16;");
writer.WriteLine("byte converted = unchecked((byte)x);"); writer.WriteLine("byte converted = unchecked((byte)x);");
writer.WriteLine("return Unsafe.As<byte, TTo>(ref converted);"); writer.WriteLine("return Unsafe.BitCast<byte, TTo>(converted);");
}
else if (from.Size < sizeof(double) && to.Size < sizeof(double))
{
writer.WriteLine($"double interpolated = (double)value / (double){from.MaxValue};");
writer.WriteLine($"double exact = interpolated * (double){to.MaxValue};");
writer.WriteLine($"{to.LangName} converted = ({to.LangName})double.Round(exact, MidpointRounding.AwayFromZero);");
writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
else else
{ {
@@ -236,7 +245,7 @@ internal static class NumericConversionGenerator
{ {
int offset = (from.Size / to.Size - 1) * to.Size * 8; int offset = (from.Size / to.Size - 1) * to.Size * 8;
writer.WriteLine($"{to.LangName} converted = ({to.LangName})(({conversionType})value >> {offset});"); writer.WriteLine($"{to.LangName} converted = ({to.LangName})(({conversionType})value >> {offset});");
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);"); writer.WriteLine($"return Unsafe.BitCast<{to.LangName}, TTo>(converted);");
} }
} }
} }
@@ -267,7 +276,7 @@ internal static class NumericConversionGenerator
using (new CurlyBrackets(writer)) using (new CurlyBrackets(writer))
{ {
writer.WriteLine($"{from.LangName} value = {methodName}Safe<{from.LangName}>();"); writer.WriteLine($"{from.LangName} value = {methodName}Safe<{from.LangName}>();");
writer.WriteLine($"return Unsafe.As<{from.LangName}, T>(ref value);"); writer.WriteLine($"return Unsafe.BitCast<{from.LangName}, T>(value);");
} }
} }
writer.WriteLine("else"); writer.WriteLine("else");
@@ -1,6 +1,7 @@
using AssetRipper.Text.SourceGeneration; using AssetRipper.Text.SourceGeneration;
using AssetRipper.TextureDecoder.SourceGeneration.Common; using AssetRipper.TextureDecoder.SourceGeneration.Common;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Text; using System.Text;
namespace AssetRipper.TextureDecoder.ColorGenerator; namespace AssetRipper.TextureDecoder.ColorGenerator;
@@ -12,38 +13,41 @@ internal static partial class Program
private const string FormatsNamespace = "AssetRipper.TextureDecoder.Rgb.Formats"; private const string FormatsNamespace = "AssetRipper.TextureDecoder.Rgb.Formats";
private const string FormatsFolder = "../../../../AssetRipper.TextureDecoder/Rgb/Formats/"; private const string FormatsFolder = "../../../../AssetRipper.TextureDecoder/Rgb/Formats/";
/// <summary> private readonly record struct CustomColorDetails(string Name, Type Type, bool HasRed, bool HasGreen, bool HasBlue, bool HasAlpha, bool FullyUtilized)
/// Name, Type, Red, Blue, Green, Alpha, Fully Utilized
/// </summary>
private static readonly List<(string, Type, bool, bool, bool, bool, bool)> CustomColors = new()
{ {
public static implicit operator CustomColorDetails((string, Type, bool, bool, bool, bool, bool) details)
{
return new CustomColorDetails(details.Item1, details.Item2, details.Item3, details.Item4, details.Item5, details.Item6, details.Item7);
}
}
private static readonly CustomColorDetails[] CustomColors =
[
( "ColorARGB16", typeof(byte), true, true, true, true, false ), ( "ColorARGB16", typeof(byte), true, true, true, true, false ),
( "ColorARGB32", typeof(byte), true, true, true, true, true ),
( "ColorBGRA32", typeof(byte), true, true, true, true, true ),
( "ColorRGB16", typeof(byte), true, true, true, false, false ), ( "ColorRGB16", typeof(byte), true, true, true, false, false ),
( "ColorRGB9e5", typeof(double), true, true, true, false, false ), ( "ColorRGB9e5", typeof(double), true, true, true, false, false ),
( "ColorRGBA16", typeof(byte), true, true, true, true, false ), ( "ColorRGBA16", typeof(byte), true, true, true, true, false ),
}; ];
/// <summary> private static readonly string[] GenericColors =
/// Name, Red, Blue, Green, Alpha [
/// </summary> "ColorR",
private static readonly List<(string, bool, bool, bool, bool)> GenericColors = new() "ColorRG",
{ "ColorRGB",
( "ColorR", true, false, false, false ), "ColorRGBA",
( "ColorRG", true, true, false, false ), "ColorA",
( "ColorRGB", true, true, true, false ), "ColorARGB",
( "ColorRGBA", true, true, true, true ), "ColorBGRA",
( "ColorA", false, false, false, true ), "ColorBGR",
}; ];
static void Main() static void Main()
{ {
foreach (var customColor in CustomColors) foreach (CustomColorDetails customColor in CustomColors)
{ {
WriteCustomColor(customColor); WriteCustomColor(customColor);
} }
foreach (var genericColor in GenericColors) foreach (string genericColor in GenericColors)
{ {
WriteGenericColor(genericColor); WriteGenericColor(genericColor);
} }
@@ -55,7 +59,7 @@ internal static partial class Program
Console.WriteLine("Done!"); Console.WriteLine("Done!");
} }
private static void WriteCustomColor((string, Type, bool, bool, bool, bool, bool) details) private static void WriteCustomColor(CustomColorDetails details)
{ {
(string name, Type type, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, bool fullyUtilized) = details; (string name, Type type, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, bool fullyUtilized) = details;
Console.WriteLine(name); Console.WriteLine(name);
@@ -63,12 +67,11 @@ internal static partial class Program
WriteCustomColor(writer, name, type, hasRed, hasGreen, hasBlue, hasAlpha, fullyUtilized); WriteCustomColor(writer, name, type, hasRed, hasGreen, hasBlue, hasAlpha, fullyUtilized);
} }
private static void WriteGenericColor((string, bool, bool, bool, bool) details) private static void WriteGenericColor(string name)
{ {
(string name, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha) = details;
Console.WriteLine(name); Console.WriteLine(name);
using IndentedTextWriter writer = IndentedTextWriterFactory.Create(FormatsFolder, name); using IndentedTextWriter writer = IndentedTextWriterFactory.Create(FormatsFolder, name);
WriteGenericColor(writer, name, hasRed, hasGreen, hasBlue, hasAlpha); WriteGenericColor(writer, name);
} }
private static void WriteSuperGenericColor(int channelCount) private static void WriteSuperGenericColor(int channelCount)
@@ -214,7 +217,7 @@ internal static partial class Program
} }
} }
private static void WriteGenericColor(IndentedTextWriter writer, string name, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha) private static void WriteGenericColor(IndentedTextWriter writer, string name)
{ {
const string typeName = "T"; const string typeName = "T";
const string minValue = "NumericConversion.GetMinimumValueSafe<T>()"; const string minValue = "NumericConversion.GetMinimumValueSafe<T>()";
@@ -229,14 +232,39 @@ internal static partial class Program
writer.WriteLine($"public partial struct {name}<T> : IColor<{name}<T>, T> where T : unmanaged, INumberBase<T>, IMinMaxValue<T>"); writer.WriteLine($"public partial struct {name}<T> : IColor<{name}<T>, T> where T : unmanaged, INumberBase<T>, IMinMaxValue<T>");
using (new CurlyBrackets(writer)) using (new CurlyBrackets(writer))
{ {
WriteProperty(writer, hasRed, typeName, 'R', minValue); Debug.Assert(name.StartsWith("Color", StringComparison.Ordinal));
writer.WriteLineNoTabs(); ReadOnlySpan<char> channels = name.AsSpan()[5..];
WriteProperty(writer, hasGreen, typeName, 'G', minValue); foreach (char channel in channels)
writer.WriteLineNoTabs(); {
WriteProperty(writer, hasBlue, typeName, 'B', minValue); WriteProperty(writer, true, typeName, channel, minValue);
writer.WriteLineNoTabs(); writer.WriteLineNoTabs();
WriteProperty(writer, hasAlpha, typeName, 'A', maxValue); }
writer.WriteLineNoTabs();
bool hasRed = channels.Contains('R');
bool hasGreen = channels.Contains('G');
bool hasBlue = channels.Contains('B');
bool hasAlpha = channels.Contains('A');
if (!hasRed)
{
WriteProperty(writer, hasRed, typeName, 'R', minValue);
writer.WriteLineNoTabs();
}
if (!hasGreen)
{
WriteProperty(writer, hasGreen, typeName, 'G', minValue);
writer.WriteLineNoTabs();
}
if (!hasBlue)
{
WriteProperty(writer, hasBlue, typeName, 'B', minValue);
writer.WriteLineNoTabs();
}
if (!hasAlpha)
{
WriteProperty(writer, hasAlpha, typeName, 'A', maxValue);
writer.WriteLineNoTabs();
}
WriteConstructor(writer, name, hasRed, hasGreen, hasBlue, hasAlpha, typeName); WriteConstructor(writer, name, hasRed, hasGreen, hasBlue, hasAlpha, typeName);
writer.WriteLineNoTabs(); writer.WriteLineNoTabs();
@@ -27,7 +27,7 @@ internal static partial class Program
string args6 = args.GetArgument(6); string args6 = args.GetArgument(6);
byte[] data = File.ReadAllBytes(path); byte[] data = File.ReadAllBytes(path);
DirectBitmap<ColorBGRA32, byte> bitmap = new DirectBitmap<ColorBGRA32, byte>(width, height); DirectBitmap<ColorBGRA<byte>, byte> bitmap = new DirectBitmap<ColorBGRA<byte>, byte>(width, height);
switch (inputType) switch (inputType)
{ {
case "astc": case "astc":
@@ -52,43 +52,40 @@ internal static partial class Program
DecodeRgb(data, width, height, args5, bitmap.Bits); DecodeRgb(data, width, height, args5, bitmap.Bits);
break; break;
case "yuy2": case "yuy2":
Yuy2Decoder.DecompressYUY2(data, width, height, bitmap.Bits); Yuy2Decoder.DecompressYUY2<ColorBGRA<byte>, byte>(data, width, height, bitmap.Bits);
break; break;
default: default:
throw new NotSupportedException(inputType); throw new NotSupportedException(inputType);
} }
string dirPath = Path.GetDirectoryName(path) ?? Environment.CurrentDirectory;
string name = Path.GetFileName(path);
switch (outputType) switch (outputType)
{ {
case "bgra": case "bgra":
WriteAllBytes(Path.Combine(dirPath, name + ".bgra"), bitmap.Bits); File.WriteAllBytes(path + ".bgra", bitmap.Bits);
break; break;
case "bmp": case "bmp":
bitmap.FlipY(); bitmap.FlipY();
bitmap.SaveAsBmp(Path.Combine(dirPath, name + ".bmp")); bitmap.SaveAsBmp(path + ".bmp");
break; break;
case "exr": case "exr":
bitmap.FlipY(); bitmap.FlipY();
bitmap.SaveAsExr(Path.Combine(dirPath, name + ".exr")); bitmap.SaveAsExr(path + ".exr");
break; break;
case "hdr": case "hdr":
bitmap.FlipY(); bitmap.FlipY();
bitmap.SaveAsHdr(Path.Combine(dirPath, name + ".hdr")); bitmap.SaveAsHdr(path + ".hdr");
break; break;
case "jpg": case "jpg":
bitmap.FlipY(); bitmap.FlipY();
bitmap.SaveAsJpg(Path.Combine(dirPath, name + ".jpg")); bitmap.SaveAsJpg(path + ".jpg");
break; break;
case "png": case "png":
bitmap.FlipY(); bitmap.FlipY();
bitmap.SaveAsPng(Path.Combine(dirPath, name + ".png")); bitmap.SaveAsPng(path + ".png");
break; break;
case "tga": case "tga":
bitmap.FlipY(); bitmap.FlipY();
bitmap.SaveAsTga(Path.Combine(dirPath, name + ".tga")); bitmap.SaveAsTga(path + ".tga");
break; break;
default: default:
throw new NotSupportedException(outputType); throw new NotSupportedException(outputType);
@@ -108,7 +105,7 @@ internal static partial class Program
Console.WriteLine("Arg at index 6 : blockYSize"); Console.WriteLine("Arg at index 6 : blockYSize");
int blockXSize = int.Parse(blockXSizeString); int blockXSize = int.Parse(blockXSizeString);
int blockYSize = int.Parse(blockYSizeString); int blockYSize = int.Parse(blockYSizeString);
AstcDecoder.DecodeASTC(input, width, height, blockXSize, blockYSize, output); AstcDecoder.DecodeASTC<ColorBGRA<byte>, byte>(input, width, height, blockXSize, blockYSize, output);
} }
private static void DecodeAtc(ReadOnlySpan<byte> input, int width, int height, string modeString, Span<byte> 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) switch (mode)
{ {
case 0: case 0:
AtcDecoder.DecompressAtcRgb4(input, width, height, output); AtcDecoder.DecompressAtcRgb4<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 1: case 1:
AtcDecoder.DecompressAtcRgba8(input, width, height, output); AtcDecoder.DecompressAtcRgba8<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
default: default:
@@ -146,25 +143,25 @@ internal static partial class Program
switch (mode) switch (mode)
{ {
case 1: case 1:
Bc1.Decompress(input, width, height, output); Bc1.Decompress<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 2: case 2:
Bc2.Decompress(input, width, height, output); Bc2.Decompress<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 3: case 3:
Bc3.Decompress(input, width, height, output); Bc3.Decompress<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 4: case 4:
Bc4.Decompress(input, width, height, output); Bc4.Decompress<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 5: case 5:
Bc5.Decompress(input, width, height, output); Bc5.Decompress<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 6: case 6:
Bc6h.Decompress(input, width, height, bool.Parse(isSignedString), output); Bc6h.Decompress<ColorBGRA<byte>, byte>(input, width, height, bool.Parse(isSignedString), output);
break; break;
case 7: case 7:
Bc7.Decompress(input, width, height, output); Bc7.Decompress<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
default: default:
@@ -182,13 +179,13 @@ internal static partial class Program
switch (mode) switch (mode)
{ {
case 0: case 0:
DxtDecoder.DecompressDXT1(input, width, height, output); DxtDecoder.DecompressDXT1<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 1: case 1:
DxtDecoder.DecompressDXT3(input, width, height, output); DxtDecoder.DecompressDXT3<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 2: case 2:
DxtDecoder.DecompressDXT5(input, width, height, output); DxtDecoder.DecompressDXT5<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
default: default:
@@ -211,28 +208,28 @@ internal static partial class Program
switch (mode) switch (mode)
{ {
case 0: case 0:
EtcDecoder.DecompressETC(input, width, height, output); EtcDecoder.DecompressETC<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 1: case 1:
EtcDecoder.DecompressETC2(input, width, height, output); EtcDecoder.DecompressETC2<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 2: case 2:
EtcDecoder.DecompressETC2A1(input, width, height, output); EtcDecoder.DecompressETC2A1<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 3: case 3:
EtcDecoder.DecompressETC2A8(input, width, height, output); EtcDecoder.DecompressETC2A8<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 4: case 4:
EtcDecoder.DecompressEACRUnsigned(input, width, height, output); EtcDecoder.DecompressEACRUnsigned<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 5: case 5:
EtcDecoder.DecompressEACRSigned(input, width, height, output); EtcDecoder.DecompressEACRSigned<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 6: case 6:
EtcDecoder.DecompressEACRGUnsigned(input, width, height, output); EtcDecoder.DecompressEACRGUnsigned<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
case 7: case 7:
EtcDecoder.DecompressEACRGSigned(input, width, height, output); EtcDecoder.DecompressEACRGSigned<ColorBGRA<byte>, byte>(input, width, height, output);
break; break;
default: default:
@@ -244,13 +241,6 @@ internal static partial class Program
{ {
Console.WriteLine("Arg at index 5 : 2bitMode"); Console.WriteLine("Arg at index 5 : 2bitMode");
bool do2bit = bool.Parse(do2bitModeString); bool do2bit = bool.Parse(do2bitModeString);
PvrtcDecoder.DecompressPVRTC(input, width, height, do2bit, output); PvrtcDecoder.DecompressPVRTC<ColorBGRA<byte>, byte>(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);
} }
} }
+306 -177
View File
@@ -1,6 +1,7 @@
// Auto-generated code. Do not modify manually. // Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb; using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Channels;
using AssetRipper.TextureDecoder.Rgb.Formats; using AssetRipper.TextureDecoder.Rgb.Formats;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -15,367 +16,495 @@ static partial class Program
Console.WriteLine(" 2 - RGB_SByte"); Console.WriteLine(" 2 - RGB_SByte");
Console.WriteLine(" 3 - RGBA_SByte"); Console.WriteLine(" 3 - RGBA_SByte");
Console.WriteLine(" 4 - A_SByte"); Console.WriteLine(" 4 - A_SByte");
Console.WriteLine(" 5 - R_Byte"); Console.WriteLine(" 5 - ARGB_SByte");
Console.WriteLine(" 6 - RG_Byte"); Console.WriteLine(" 6 - BGRA_SByte");
Console.WriteLine(" 7 - RGB_Byte"); Console.WriteLine(" 7 - R_Byte");
Console.WriteLine(" 8 - RGBA_Byte"); Console.WriteLine(" 8 - RG_Byte");
Console.WriteLine(" 9 - A_Byte"); Console.WriteLine(" 9 - RGB_Byte");
Console.WriteLine(" 10 - R_Int16"); Console.WriteLine(" 10 - RGBA_Byte");
Console.WriteLine(" 11 - RG_Int16"); Console.WriteLine(" 11 - A_Byte");
Console.WriteLine(" 12 - RGB_Int16"); Console.WriteLine(" 12 - ARGB_Byte");
Console.WriteLine(" 13 - RGBA_Int16"); Console.WriteLine(" 13 - BGRA_Byte");
Console.WriteLine(" 14 - A_Int16"); Console.WriteLine(" 14 - R_Int16");
Console.WriteLine(" 15 - R_UInt16"); Console.WriteLine(" 15 - RG_Int16");
Console.WriteLine(" 16 - RG_UInt16"); Console.WriteLine(" 16 - RGB_Int16");
Console.WriteLine(" 17 - RGB_UInt16"); Console.WriteLine(" 17 - RGBA_Int16");
Console.WriteLine(" 18 - RGBA_UInt16"); Console.WriteLine(" 18 - A_Int16");
Console.WriteLine(" 19 - A_UInt16"); Console.WriteLine(" 19 - ARGB_Int16");
Console.WriteLine(" 20 - R_Int32"); Console.WriteLine(" 20 - BGRA_Int16");
Console.WriteLine(" 21 - RG_Int32"); Console.WriteLine(" 21 - R_UInt16");
Console.WriteLine(" 22 - RGB_Int32"); Console.WriteLine(" 22 - RG_UInt16");
Console.WriteLine(" 23 - RGBA_Int32"); Console.WriteLine(" 23 - RGB_UInt16");
Console.WriteLine(" 24 - A_Int32"); Console.WriteLine(" 24 - RGBA_UInt16");
Console.WriteLine(" 25 - R_UInt32"); Console.WriteLine(" 25 - A_UInt16");
Console.WriteLine(" 26 - RG_UInt32"); Console.WriteLine(" 26 - ARGB_UInt16");
Console.WriteLine(" 27 - RGB_UInt32"); Console.WriteLine(" 27 - BGRA_UInt16");
Console.WriteLine(" 28 - RGBA_UInt32"); Console.WriteLine(" 28 - R_Int32");
Console.WriteLine(" 29 - A_UInt32"); Console.WriteLine(" 29 - RG_Int32");
Console.WriteLine(" 30 - R_IntPtr"); Console.WriteLine(" 30 - RGB_Int32");
Console.WriteLine(" 31 - RG_IntPtr"); Console.WriteLine(" 31 - RGBA_Int32");
Console.WriteLine(" 32 - RGB_IntPtr"); Console.WriteLine(" 32 - A_Int32");
Console.WriteLine(" 33 - RGBA_IntPtr"); Console.WriteLine(" 33 - ARGB_Int32");
Console.WriteLine(" 34 - A_IntPtr"); Console.WriteLine(" 34 - BGRA_Int32");
Console.WriteLine(" 35 - R_UIntPtr"); Console.WriteLine(" 35 - R_UInt32");
Console.WriteLine(" 36 - RG_UIntPtr"); Console.WriteLine(" 36 - RG_UInt32");
Console.WriteLine(" 37 - RGB_UIntPtr"); Console.WriteLine(" 37 - RGB_UInt32");
Console.WriteLine(" 38 - RGBA_UIntPtr"); Console.WriteLine(" 38 - RGBA_UInt32");
Console.WriteLine(" 39 - A_UIntPtr"); Console.WriteLine(" 39 - A_UInt32");
Console.WriteLine(" 40 - R_Int64"); Console.WriteLine(" 40 - ARGB_UInt32");
Console.WriteLine(" 41 - RG_Int64"); Console.WriteLine(" 41 - BGRA_UInt32");
Console.WriteLine(" 42 - RGB_Int64"); Console.WriteLine(" 42 - R_IntPtr");
Console.WriteLine(" 43 - RGBA_Int64"); Console.WriteLine(" 43 - RG_IntPtr");
Console.WriteLine(" 44 - A_Int64"); Console.WriteLine(" 44 - RGB_IntPtr");
Console.WriteLine(" 45 - R_UInt64"); Console.WriteLine(" 45 - RGBA_IntPtr");
Console.WriteLine(" 46 - RG_UInt64"); Console.WriteLine(" 46 - A_IntPtr");
Console.WriteLine(" 47 - RGB_UInt64"); Console.WriteLine(" 47 - ARGB_IntPtr");
Console.WriteLine(" 48 - RGBA_UInt64"); Console.WriteLine(" 48 - BGRA_IntPtr");
Console.WriteLine(" 49 - A_UInt64"); Console.WriteLine(" 49 - R_UIntPtr");
Console.WriteLine(" 50 - R_Int128"); Console.WriteLine(" 50 - RG_UIntPtr");
Console.WriteLine(" 51 - RG_Int128"); Console.WriteLine(" 51 - RGB_UIntPtr");
Console.WriteLine(" 52 - RGB_Int128"); Console.WriteLine(" 52 - RGBA_UIntPtr");
Console.WriteLine(" 53 - RGBA_Int128"); Console.WriteLine(" 53 - A_UIntPtr");
Console.WriteLine(" 54 - A_Int128"); Console.WriteLine(" 54 - ARGB_UIntPtr");
Console.WriteLine(" 55 - R_UInt128"); Console.WriteLine(" 55 - BGRA_UIntPtr");
Console.WriteLine(" 56 - RG_UInt128"); Console.WriteLine(" 56 - R_Int64");
Console.WriteLine(" 57 - RGB_UInt128"); Console.WriteLine(" 57 - RG_Int64");
Console.WriteLine(" 58 - RGBA_UInt128"); Console.WriteLine(" 58 - RGB_Int64");
Console.WriteLine(" 59 - A_UInt128"); Console.WriteLine(" 59 - RGBA_Int64");
Console.WriteLine(" 60 - R_Half"); Console.WriteLine(" 60 - A_Int64");
Console.WriteLine(" 61 - RG_Half"); Console.WriteLine(" 61 - ARGB_Int64");
Console.WriteLine(" 62 - RGB_Half"); Console.WriteLine(" 62 - BGRA_Int64");
Console.WriteLine(" 63 - RGBA_Half"); Console.WriteLine(" 63 - R_UInt64");
Console.WriteLine(" 64 - A_Half"); Console.WriteLine(" 64 - RG_UInt64");
Console.WriteLine(" 65 - R_Single"); Console.WriteLine(" 65 - RGB_UInt64");
Console.WriteLine(" 66 - RG_Single"); Console.WriteLine(" 66 - RGBA_UInt64");
Console.WriteLine(" 67 - RGB_Single"); Console.WriteLine(" 67 - A_UInt64");
Console.WriteLine(" 68 - RGBA_Single"); Console.WriteLine(" 68 - ARGB_UInt64");
Console.WriteLine(" 69 - A_Single"); Console.WriteLine(" 69 - BGRA_UInt64");
Console.WriteLine(" 70 - R_NFloat"); Console.WriteLine(" 70 - R_Int128");
Console.WriteLine(" 71 - RG_NFloat"); Console.WriteLine(" 71 - RG_Int128");
Console.WriteLine(" 72 - RGB_NFloat"); Console.WriteLine(" 72 - RGB_Int128");
Console.WriteLine(" 73 - RGBA_NFloat"); Console.WriteLine(" 73 - RGBA_Int128");
Console.WriteLine(" 74 - A_NFloat"); Console.WriteLine(" 74 - A_Int128");
Console.WriteLine(" 75 - R_Double"); Console.WriteLine(" 75 - ARGB_Int128");
Console.WriteLine(" 76 - RG_Double"); Console.WriteLine(" 76 - BGRA_Int128");
Console.WriteLine(" 77 - RGB_Double"); Console.WriteLine(" 77 - R_UInt128");
Console.WriteLine(" 78 - RGBA_Double"); Console.WriteLine(" 78 - RG_UInt128");
Console.WriteLine(" 79 - A_Double"); Console.WriteLine(" 79 - RGB_UInt128");
Console.WriteLine(" 80 - R_Decimal"); Console.WriteLine(" 80 - RGBA_UInt128");
Console.WriteLine(" 81 - RG_Decimal"); Console.WriteLine(" 81 - A_UInt128");
Console.WriteLine(" 82 - RGB_Decimal"); Console.WriteLine(" 82 - ARGB_UInt128");
Console.WriteLine(" 83 - RGBA_Decimal"); Console.WriteLine(" 83 - BGRA_UInt128");
Console.WriteLine(" 84 - A_Decimal"); Console.WriteLine(" 84 - R_Half");
Console.WriteLine(" 85 - ARGB16"); Console.WriteLine(" 85 - RG_Half");
Console.WriteLine(" 86 - ARGB32"); Console.WriteLine(" 86 - RGB_Half");
Console.WriteLine(" 87 - BGRA32"); Console.WriteLine(" 87 - RGBA_Half");
Console.WriteLine(" 88 - RGB16"); Console.WriteLine(" 88 - A_Half");
Console.WriteLine(" 89 - RGB9e5"); Console.WriteLine(" 89 - ARGB_Half");
Console.WriteLine(" 90 - RGBA16"); Console.WriteLine(" 90 - BGRA_Half");
Console.WriteLine(" 91 - R_Single");
Console.WriteLine(" 92 - RG_Single");
Console.WriteLine(" 93 - RGB_Single");
Console.WriteLine(" 94 - RGBA_Single");
Console.WriteLine(" 95 - A_Single");
Console.WriteLine(" 96 - ARGB_Single");
Console.WriteLine(" 97 - BGRA_Single");
Console.WriteLine(" 98 - R_NFloat");
Console.WriteLine(" 99 - RG_NFloat");
Console.WriteLine(" 100 - RGB_NFloat");
Console.WriteLine(" 101 - RGBA_NFloat");
Console.WriteLine(" 102 - A_NFloat");
Console.WriteLine(" 103 - ARGB_NFloat");
Console.WriteLine(" 104 - BGRA_NFloat");
Console.WriteLine(" 105 - R_Double");
Console.WriteLine(" 106 - RG_Double");
Console.WriteLine(" 107 - RGB_Double");
Console.WriteLine(" 108 - RGBA_Double");
Console.WriteLine(" 109 - A_Double");
Console.WriteLine(" 110 - ARGB_Double");
Console.WriteLine(" 111 - BGRA_Double");
Console.WriteLine(" 112 - R_Decimal");
Console.WriteLine(" 113 - RG_Decimal");
Console.WriteLine(" 114 - RGB_Decimal");
Console.WriteLine(" 115 - RGBA_Decimal");
Console.WriteLine(" 116 - A_Decimal");
Console.WriteLine(" 117 - ARGB_Decimal");
Console.WriteLine(" 118 - BGRA_Decimal");
Console.WriteLine(" 119 - ARGB16");
Console.WriteLine(" 120 - RGB16");
Console.WriteLine(" 121 - RGB9e5");
Console.WriteLine(" 122 - RGBA16");
int mode = int.Parse(modeString); int mode = int.Parse(modeString);
switch(mode) switch(mode)
{ {
case 0: case 0:
RgbConverter.Convert<ColorR<sbyte>, sbyte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<sbyte>, sbyte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 1: case 1:
RgbConverter.Convert<ColorRG<sbyte>, sbyte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<sbyte>, sbyte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 2: case 2:
RgbConverter.Convert<ColorRGB<sbyte>, sbyte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<sbyte>, sbyte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 3: case 3:
RgbConverter.Convert<ColorRGBA<sbyte>, sbyte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<sbyte>, sbyte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 4: case 4:
RgbConverter.Convert<ColorA<sbyte>, sbyte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<sbyte>, sbyte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 5: case 5:
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<sbyte>, sbyte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 6: case 6:
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<sbyte>, sbyte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 7: case 7:
RgbConverter.Convert<ColorRGB<byte>, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<byte>, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 8: case 8:
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<byte>, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 9: case 9:
RgbConverter.Convert<ColorA<byte>, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<byte>, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 10: case 10:
RgbConverter.Convert<ColorR<short>, short, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<byte>, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 11: case 11:
RgbConverter.Convert<ColorRG<short>, short, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<byte>, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 12: case 12:
RgbConverter.Convert<ColorRGB<short>, short, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<byte>, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 13: case 13:
RgbConverter.Convert<ColorRGBA<short>, short, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<byte>, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 14: case 14:
RgbConverter.Convert<ColorA<short>, short, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<short>, short, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 15: case 15:
RgbConverter.Convert<ColorR<ushort>, ushort, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<short>, short, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 16: case 16:
RgbConverter.Convert<ColorRG<ushort>, ushort, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<short>, short, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 17: case 17:
RgbConverter.Convert<ColorRGB<ushort>, ushort, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<short>, short, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 18: case 18:
RgbConverter.Convert<ColorRGBA<ushort>, ushort, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<short>, short, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 19: case 19:
RgbConverter.Convert<ColorA<ushort>, ushort, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<short>, short, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 20: case 20:
RgbConverter.Convert<ColorR<int>, int, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<short>, short, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 21: case 21:
RgbConverter.Convert<ColorRG<int>, int, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<ushort>, ushort, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 22: case 22:
RgbConverter.Convert<ColorRGB<int>, int, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<ushort>, ushort, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 23: case 23:
RgbConverter.Convert<ColorRGBA<int>, int, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<ushort>, ushort, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 24: case 24:
RgbConverter.Convert<ColorA<int>, int, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<ushort>, ushort, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 25: case 25:
RgbConverter.Convert<ColorR<uint>, uint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<ushort>, ushort, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 26: case 26:
RgbConverter.Convert<ColorRG<uint>, uint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<ushort>, ushort, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 27: case 27:
RgbConverter.Convert<ColorRGB<uint>, uint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<ushort>, ushort, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 28: case 28:
RgbConverter.Convert<ColorRGBA<uint>, uint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<int>, int, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 29: case 29:
RgbConverter.Convert<ColorA<uint>, uint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<int>, int, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 30: case 30:
RgbConverter.Convert<ColorR<nint>, nint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<int>, int, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 31: case 31:
RgbConverter.Convert<ColorRG<nint>, nint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<int>, int, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 32: case 32:
RgbConverter.Convert<ColorRGB<nint>, nint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<int>, int, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 33: case 33:
RgbConverter.Convert<ColorRGBA<nint>, nint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<int>, int, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 34: case 34:
RgbConverter.Convert<ColorA<nint>, nint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<int>, int, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 35: case 35:
RgbConverter.Convert<ColorR<nuint>, nuint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<uint>, uint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 36: case 36:
RgbConverter.Convert<ColorRG<nuint>, nuint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<uint>, uint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 37: case 37:
RgbConverter.Convert<ColorRGB<nuint>, nuint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<uint>, uint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 38: case 38:
RgbConverter.Convert<ColorRGBA<nuint>, nuint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<uint>, uint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 39: case 39:
RgbConverter.Convert<ColorA<nuint>, nuint, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<uint>, uint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 40: case 40:
RgbConverter.Convert<ColorR<long>, long, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<uint>, uint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 41: case 41:
RgbConverter.Convert<ColorRG<long>, long, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<uint>, uint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 42: case 42:
RgbConverter.Convert<ColorRGB<long>, long, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<nint>, nint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 43: case 43:
RgbConverter.Convert<ColorRGBA<long>, long, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<nint>, nint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 44: case 44:
RgbConverter.Convert<ColorA<long>, long, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<nint>, nint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 45: case 45:
RgbConverter.Convert<ColorR<ulong>, ulong, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<nint>, nint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 46: case 46:
RgbConverter.Convert<ColorRG<ulong>, ulong, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<nint>, nint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 47: case 47:
RgbConverter.Convert<ColorRGB<ulong>, ulong, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<nint>, nint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 48: case 48:
RgbConverter.Convert<ColorRGBA<ulong>, ulong, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<nint>, nint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 49: case 49:
RgbConverter.Convert<ColorA<ulong>, ulong, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<nuint>, nuint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 50: case 50:
RgbConverter.Convert<ColorR<Int128>, Int128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<nuint>, nuint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 51: case 51:
RgbConverter.Convert<ColorRG<Int128>, Int128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<nuint>, nuint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 52: case 52:
RgbConverter.Convert<ColorRGB<Int128>, Int128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<nuint>, nuint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 53: case 53:
RgbConverter.Convert<ColorRGBA<Int128>, Int128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<nuint>, nuint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 54: case 54:
RgbConverter.Convert<ColorA<Int128>, Int128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<nuint>, nuint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 55: case 55:
RgbConverter.Convert<ColorR<UInt128>, UInt128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<nuint>, nuint, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 56: case 56:
RgbConverter.Convert<ColorRG<UInt128>, UInt128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<long>, long, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 57: case 57:
RgbConverter.Convert<ColorRGB<UInt128>, UInt128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<long>, long, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 58: case 58:
RgbConverter.Convert<ColorRGBA<UInt128>, UInt128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<long>, long, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 59: case 59:
RgbConverter.Convert<ColorA<UInt128>, UInt128, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<long>, long, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 60: case 60:
RgbConverter.Convert<ColorR<Half>, Half, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<long>, long, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 61: case 61:
RgbConverter.Convert<ColorRG<Half>, Half, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<long>, long, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 62: case 62:
RgbConverter.Convert<ColorRGB<Half>, Half, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<long>, long, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 63: case 63:
RgbConverter.Convert<ColorRGBA<Half>, Half, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<ulong>, ulong, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 64: case 64:
RgbConverter.Convert<ColorA<Half>, Half, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<ulong>, ulong, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 65: case 65:
RgbConverter.Convert<ColorR<float>, float, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<ulong>, ulong, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 66: case 66:
RgbConverter.Convert<ColorRG<float>, float, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<ulong>, ulong, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 67: case 67:
RgbConverter.Convert<ColorRGB<float>, float, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<ulong>, ulong, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 68: case 68:
RgbConverter.Convert<ColorRGBA<float>, float, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<ulong>, ulong, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 69: case 69:
RgbConverter.Convert<ColorA<float>, float, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<ulong>, ulong, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 70: case 70:
RgbConverter.Convert<ColorR<NFloat>, NFloat, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<Int128>, Int128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 71: case 71:
RgbConverter.Convert<ColorRG<NFloat>, NFloat, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<Int128>, Int128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 72: case 72:
RgbConverter.Convert<ColorRGB<NFloat>, NFloat, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<Int128>, Int128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 73: case 73:
RgbConverter.Convert<ColorRGBA<NFloat>, NFloat, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<Int128>, Int128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 74: case 74:
RgbConverter.Convert<ColorA<NFloat>, NFloat, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<Int128>, Int128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 75: case 75:
RgbConverter.Convert<ColorR<double>, double, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<Int128>, Int128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 76: case 76:
RgbConverter.Convert<ColorRG<double>, double, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<Int128>, Int128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 77: case 77:
RgbConverter.Convert<ColorRGB<double>, double, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<UInt128>, UInt128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 78: case 78:
RgbConverter.Convert<ColorRGBA<double>, double, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<UInt128>, UInt128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 79: case 79:
RgbConverter.Convert<ColorA<double>, double, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<UInt128>, UInt128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 80: case 80:
RgbConverter.Convert<ColorR<decimal>, decimal, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<UInt128>, UInt128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 81: case 81:
RgbConverter.Convert<ColorRG<decimal>, decimal, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<UInt128>, UInt128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 82: case 82:
RgbConverter.Convert<ColorRGB<decimal>, decimal, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<UInt128>, UInt128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 83: case 83:
RgbConverter.Convert<ColorRGBA<decimal>, decimal, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<UInt128>, UInt128, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 84: case 84:
RgbConverter.Convert<ColorA<decimal>, decimal, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorR<Half>, Half, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 85: case 85:
RgbConverter.Convert<ColorARGB16, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRG<Half>, Half, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 86: case 86:
RgbConverter.Convert<ColorARGB32, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGB<Half>, Half, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 87: case 87:
RgbConverter.Convert<ColorBGRA32, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorRGBA<Half>, Half, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 88: case 88:
RgbConverter.Convert<ColorRGB16, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorA<Half>, Half, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 89: case 89:
RgbConverter.Convert<ColorRGB9e5, double, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorARGB<Half>, Half, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
case 90: case 90:
RgbConverter.Convert<ColorRGBA16, byte, ColorBGRA32, byte>(input, width, height, output); RgbConverter.Convert<ColorBGRA<Half>, Half, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 91:
RgbConverter.Convert<ColorR<float>, float, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 92:
RgbConverter.Convert<ColorRG<float>, float, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 93:
RgbConverter.Convert<ColorRGB<float>, float, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 94:
RgbConverter.Convert<ColorRGBA<float>, float, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 95:
RgbConverter.Convert<ColorA<float>, float, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 96:
RgbConverter.Convert<ColorARGB<float>, float, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 97:
RgbConverter.Convert<ColorBGRA<float>, float, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 98:
RgbConverter.Convert<ColorR<NFloat>, NFloat, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 99:
RgbConverter.Convert<ColorRG<NFloat>, NFloat, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 100:
RgbConverter.Convert<ColorRGB<NFloat>, NFloat, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 101:
RgbConverter.Convert<ColorRGBA<NFloat>, NFloat, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 102:
RgbConverter.Convert<ColorA<NFloat>, NFloat, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 103:
RgbConverter.Convert<ColorARGB<NFloat>, NFloat, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 104:
RgbConverter.Convert<ColorBGRA<NFloat>, NFloat, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 105:
RgbConverter.Convert<ColorR<double>, double, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 106:
RgbConverter.Convert<ColorRG<double>, double, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 107:
RgbConverter.Convert<ColorRGB<double>, double, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 108:
RgbConverter.Convert<ColorRGBA<double>, double, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 109:
RgbConverter.Convert<ColorA<double>, double, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 110:
RgbConverter.Convert<ColorARGB<double>, double, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 111:
RgbConverter.Convert<ColorBGRA<double>, double, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 112:
RgbConverter.Convert<ColorR<decimal>, decimal, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 113:
RgbConverter.Convert<ColorRG<decimal>, decimal, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 114:
RgbConverter.Convert<ColorRGB<decimal>, decimal, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 115:
RgbConverter.Convert<ColorRGBA<decimal>, decimal, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 116:
RgbConverter.Convert<ColorA<decimal>, decimal, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 117:
RgbConverter.Convert<ColorARGB<decimal>, decimal, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 118:
RgbConverter.Convert<ColorBGRA<decimal>, decimal, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 119:
RgbConverter.Convert<ColorARGB16, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 120:
RgbConverter.Convert<ColorRGB16, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 121:
RgbConverter.Convert<ColorRGB9e5, double, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break;
case 122:
RgbConverter.Convert<ColorRGBA16, byte, Color<byte, B, G, R, A>, byte>(input, width, height, output);
break; break;
default: default:
throw new NotSupportedException(mode.ToString()); throw new NotSupportedException(mode.ToString());
@@ -68,11 +68,18 @@ public static class CSharpPrimitives
return type == typeof(Half) || type == typeof(float) || type == typeof(NFloat) || type == typeof(double) || type == typeof(decimal); return type == typeof(Half) || type == typeof(float) || type == typeof(NFloat) || type == typeof(double) || type == typeof(decimal);
} }
public static bool IsInteger(Type type)
{
return IsUnsignedInteger(type) || IsSignedInteger(type);
}
public static bool IsUnsignedInteger(Type type) public static bool IsUnsignedInteger(Type type)
{ {
return type == typeof(byte) || type == typeof(ushort) || type == typeof(uint) || type == typeof(nuint) || type == typeof(ulong) || type == typeof(UInt128); return type == typeof(byte) || type == typeof(ushort) || type == typeof(uint) || type == typeof(nuint) || type == typeof(ulong) || type == typeof(UInt128);
} }
public static bool IsSignedInteger(Type type) => IsSignedInteger(type, out _);
public static bool IsSignedInteger(Type type, [NotNullWhen(true)] out Type? unsignedType) public static bool IsSignedInteger(Type type, [NotNullWhen(true)] out Type? unsignedType)
{ {
if (type == typeof(sbyte)) if (type == typeof(sbyte))
@@ -195,6 +202,21 @@ public static class CSharpPrimitives
} }
} }
public static Data GetDataForIntegerFloatingPointConversion(Data integerType, Data floatingPointType)
{
if (!IsInteger(integerType.Type))
{
throw new ArgumentException("The provided integer type is not an integer type.", nameof(integerType));
}
if (!IsFloatingPoint(floatingPointType.Type))
{
throw new ArgumentException("The provided floating point type is not a floating point type.", nameof(floatingPointType));
}
Data result = integerType.Size < sizeof(uint) ? Dictionary[typeof(float)] : Dictionary[typeof(double)];
return floatingPointType.Size >= result.Size ? floatingPointType : result;
}
public abstract class Data public abstract class Data
{ {
public abstract Type Type { get; } public abstract Type Type { get; }
@@ -11,7 +11,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" /> <PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,5 +1,6 @@
using AssetRipper.Text.SourceGeneration; using AssetRipper.Text.SourceGeneration;
using AssetRipper.TextureDecoder.Rgb; using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Channels;
using AssetRipper.TextureDecoder.Rgb.Formats; using AssetRipper.TextureDecoder.Rgb.Formats;
using AssetRipper.TextureDecoder.SourceGeneration.Common; using AssetRipper.TextureDecoder.SourceGeneration.Common;
using System.CodeDom.Compiler; using System.CodeDom.Compiler;
@@ -12,8 +13,6 @@ namespace AssetRipper.TextureDecoder.TestGenerator
internal static readonly List<GenerationData> dataList = new() internal static readonly List<GenerationData> dataList = new()
{ {
GenerationData.Create<ColorARGB16>(2), GenerationData.Create<ColorARGB16>(2),
GenerationData.Create<ColorARGB32>(4),
GenerationData.Create<ColorBGRA32>(4),
GenerationData.Create<ColorRGB16>(2), GenerationData.Create<ColorRGB16>(2),
GenerationData.Create<ColorRGB9e5>(4), GenerationData.Create<ColorRGB9e5>(4),
GenerationData.Create<ColorRGBA16>(2), GenerationData.Create<ColorRGBA16>(2),
@@ -35,7 +34,8 @@ namespace AssetRipper.TextureDecoder.TestGenerator
writer.WriteGeneratedCodeWarning(); writer.WriteGeneratedCodeWarning();
writer.WriteLine(); writer.WriteLine();
writer.WriteLine($"using {typeof(RgbConverter).Namespace};"); writer.WriteLine($"using {typeof(RgbConverter).Namespace};");
writer.WriteLine($"using {typeof(ColorBGRA32).Namespace};"); writer.WriteLine($"using {typeof(B).Namespace};");
writer.WriteLine($"using {typeof(ColorRGBA16).Namespace};");
writer.WriteLine($"using {typeof(NFloat).Namespace};"); writer.WriteLine($"using {typeof(NFloat).Namespace};");
writer.WriteLine(); writer.WriteLine();
@@ -75,27 +75,27 @@ internal static class TestClassGenerator
} }
textWriter.WriteLineNoTabs(); textWriter.WriteLineNoTabs();
textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorRGBA<byte>)}<{channelTypeName}>", channelTypeName); textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorRGBA<>)}<{channelTypeName}>", channelTypeName);
if (!data.AlphaChannel) if (!data.AlphaChannel)
{ {
textWriter.WriteLineNoTabs(); textWriter.WriteLineNoTabs();
textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorRGB<byte>)}<{channelTypeName}>", channelTypeName); textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorRGB<>)}<{channelTypeName}>", channelTypeName);
if (!data.BlueChannel) if (!data.BlueChannel)
{ {
textWriter.WriteLineNoTabs(); textWriter.WriteLineNoTabs();
textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorRG<byte>)}<{channelTypeName}>", channelTypeName); textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorRG<>)}<{channelTypeName}>", channelTypeName);
if (!data.GreenChannel) if (!data.GreenChannel)
{ {
textWriter.WriteLineNoTabs(); textWriter.WriteLineNoTabs();
textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorR<byte>)}<{channelTypeName}>", channelTypeName); textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorR<>)}<{channelTypeName}>", channelTypeName);
} }
} }
} }
if (!data.RedChannel && !data.GreenChannel && !data.BlueChannel) if (!data.RedChannel && !data.GreenChannel && !data.BlueChannel)
{ {
textWriter.WriteLineNoTabs(); textWriter.WriteLineNoTabs();
textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorA<byte>)}<{channelTypeName}>", channelTypeName); textWriter.WriteLosslessConversionTest(data.ColorType.Name, data.ChannelTypeName, $"{nameof(ColorA<>)}<{channelTypeName}>", channelTypeName);
} }
} }
@@ -135,11 +135,13 @@ internal static class TestClassGenerator
internal static IEnumerable<string> GetGenericColorNames() internal static IEnumerable<string> GetGenericColorNames()
{ {
yield return nameof(ColorR<byte>); yield return nameof(ColorR<>);
yield return nameof(ColorRG<byte>); yield return nameof(ColorRG<>);
yield return nameof(ColorRGB<byte>); yield return nameof(ColorRGB<>);
yield return nameof(ColorRGBA<byte>); yield return nameof(ColorRGBA<>);
yield return nameof(ColorA<byte>); yield return nameof(ColorA<>);
yield return nameof(ColorARGB<>);
yield return nameof(ColorBGRA<>);
} }
private static void MakeLosslessColorTests() private static void MakeLosslessColorTests()
@@ -218,7 +220,7 @@ internal static class TestClassGenerator
textWriter.WriteLine("var color = MakeRandomColor();"); textWriter.WriteLine("var color = MakeRandomColor();");
textWriter.WriteLine("color.GetChannels(out var r, out var g, out var b, out var a);"); textWriter.WriteLine("color.GetChannels(out var r, out var g, out var b, out var a);");
textWriter.WriteLine("Assert.Multiple(() =>"); textWriter.WriteLine("using (Assert.EnterMultipleScope())");
textWriter.WriteLine("{"); textWriter.WriteLine("{");
textWriter.Indent += 1; textWriter.Indent += 1;
@@ -228,7 +230,7 @@ internal static class TestClassGenerator
} }
textWriter.Indent -= 1; textWriter.Indent -= 1;
textWriter.WriteLine("});"); textWriter.WriteLine("};");
textWriter.Indent -= 1; textWriter.Indent -= 1;
textWriter.WriteLine("}"); textWriter.WriteLine("}");
@@ -248,7 +250,7 @@ internal static class TestClassGenerator
} }
textWriter.WriteLine($"color.{channel} = MakeRandomValue();"); textWriter.WriteLine($"color.{channel} = MakeRandomValue();");
textWriter.WriteLine("Assert.Multiple(() =>"); textWriter.WriteLine("using (Assert.EnterMultipleScope())");
textWriter.WriteLine("{"); textWriter.WriteLine("{");
textWriter.Indent += 1; textWriter.Indent += 1;
@@ -258,7 +260,7 @@ internal static class TestClassGenerator
} }
textWriter.Indent -= 1; textWriter.Indent -= 1;
textWriter.WriteLine("});"); textWriter.WriteLine("};");
textWriter.Indent -= 1; textWriter.Indent -= 1;
textWriter.WriteLine("}"); textWriter.WriteLine("}");
@@ -308,7 +310,7 @@ internal static class TestClassGenerator
textWriter.WriteLine("var color = MakeRandomColor();"); textWriter.WriteLine("var color = MakeRandomColor();");
textWriter.WriteLine("color.GetChannels(out var r, out var g, out var b, out var a);"); textWriter.WriteLine("color.GetChannels(out var r, out var g, out var b, out var a);");
textWriter.WriteLine("color.SetChannels(r, g, b, a);"); textWriter.WriteLine("color.SetChannels(r, g, b, a);");
textWriter.WriteLine("Assert.Multiple(() =>"); textWriter.WriteLine("using (Assert.EnterMultipleScope())");
textWriter.WriteLine("{"); textWriter.WriteLine("{");
textWriter.Indent += 1; textWriter.Indent += 1;
@@ -318,7 +320,7 @@ internal static class TestClassGenerator
} }
textWriter.Indent -= 1; textWriter.Indent -= 1;
textWriter.WriteLine("});"); textWriter.WriteLine("};");
textWriter.Indent -= 1; textWriter.Indent -= 1;
textWriter.WriteLine("}"); textWriter.WriteLine("}");
@@ -5,17 +5,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="NUnit" Version="4.1.0" /> <PackageReference Include="NUnit" Version="4.6.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.1.0"> <PackageReference Include="NUnit.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,4 +1,5 @@
using AssetRipper.TextureDecoder.Astc; using AssetRipper.TextureDecoder.Astc;
using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Tests; 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 private static void AssertCorrectDecompression<T>(int blockWidth, int blockHeight, double maxMeanDeviation, double maxStandardDeviation) where T : ITexture
{ {
ReadOnlySpan<byte> data = T.Data; ReadOnlySpan<byte> data = T.Data;
int bytesRead = AstcDecoder.DecodeASTC(data, T.Width, T.Height, blockWidth, blockHeight, out byte[] decompressedData); int bytesRead = AstcDecoder.DecodeASTC<ColorBGRA<byte>, byte>(data, T.Width, T.Height, blockWidth, blockHeight, out byte[] decompressedData);
if (!T.Mips) if (!T.Mips)
{ {
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
@@ -76,8 +77,10 @@ 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")] [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")]
[TestCase(new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 8, 8, TestName = "Zero blocks should not throw", Description = "Blocks of all zeros are valid")]
[TestCase(new byte[16] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, 8, 8, TestName = "0xFF blocks should not throw", Description = "Blocks with all bits set are invalid, but should be handled gracefully")]
public void DecodesBlockWithoutThrowing(byte[] block, int blockWidth, int blockHeight) public void DecodesBlockWithoutThrowing(byte[] block, int blockWidth, int blockHeight)
{ {
Assert.DoesNotThrow(() => AstcDecoder.DecodeASTC(block, blockWidth, blockHeight, blockWidth, blockHeight, out _)); Assert.DoesNotThrow(() => AstcDecoder.DecodeASTC<ColorBGRA<byte>, byte>(block, blockWidth, blockHeight, blockWidth, blockHeight, out _));
} }
} }
+9 -7
View File
@@ -1,4 +1,6 @@
namespace AssetRipper.TextureDecoder.Tests using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Tests
{ {
public sealed class AtcTests public sealed class AtcTests
{ {
@@ -6,24 +8,24 @@
public void DecompressAtcRgb4Test() public void DecompressAtcRgb4Test()
{ {
byte[] data = File.ReadAllBytes(TestFileFolders.AtcTestFiles + "test.atc_rgb4"); 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<ColorBGRA<byte>, byte>(data, 256, 256, out byte[] decodedData);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Is.EqualTo(File.ReadAllBytes(TestFileFolders.AtcTestFiles + "test.atc_rgb4_decoded"))); Assert.That(decodedData, Is.EqualTo(File.ReadAllBytes(TestFileFolders.AtcTestFiles + "test.atc_rgb4_decoded")));
}); }
} }
[Test] [Test]
public void DecompressAtcRgba8Test() public void DecompressAtcRgba8Test()
{ {
byte[] data = File.ReadAllBytes(TestFileFolders.AtcTestFiles + "test.atc_rgba8"); 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<ColorBGRA<byte>, byte>(data, 256, 256, out byte[] decodedData);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Is.EqualTo(File.ReadAllBytes(TestFileFolders.AtcTestFiles + "test.atc_rgba8_decoded"))); Assert.That(decodedData, Is.EqualTo(File.ReadAllBytes(TestFileFolders.AtcTestFiles + "test.atc_rgba8_decoded")));
}); }
} }
} }
} }
+94 -23
View File
@@ -21,14 +21,14 @@ namespace AssetRipper.TextureDecoder.Tests
} }
[Test] [Test]
public void DecompressDXT1Test() public void Decompress_BC1()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1");
int totalBytesRead = 0; 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 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); int bytesRead = Bc1.Decompress<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT1(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData); DxtDecoder.DecompressDXT1<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
AssertAlmostEqual(dxtDecodedData, bcDecodedData); AssertAlmostEqual(dxtDecodedData, bcDecodedData);
} }
@@ -36,14 +36,14 @@ namespace AssetRipper.TextureDecoder.Tests
} }
[Test] [Test]
public void DecompressDXT3Test() public void Decompress_BC2()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3");
int totalBytesRead = 0; 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 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); int bytesRead = Bc2.Decompress<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT3(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData); DxtDecoder.DecompressDXT3<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
AssertAlmostEqual(dxtDecodedData, bcDecodedData); AssertAlmostEqual(dxtDecodedData, bcDecodedData);
} }
@@ -51,14 +51,14 @@ namespace AssetRipper.TextureDecoder.Tests
} }
[Test] [Test]
public void DecompressDXT5Test() public void Decompress_BC3()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5");
int totalBytesRead = 0; 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 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); int bytesRead = Bc3.Decompress<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] bcDecodedData);
DxtDecoder.DecompressDXT5(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData); DxtDecoder.DecompressDXT5<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] dxtDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
AssertAlmostEqual(dxtDecodedData, bcDecodedData); AssertAlmostEqual(dxtDecodedData, bcDecodedData);
} }
@@ -66,25 +66,25 @@ namespace AssetRipper.TextureDecoder.Tests
} }
[Test] [Test]
public void DecompressBC4Test() public void Decompress_BC4()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4");
int bytesRead = Bc4.Decompress(data, 512, 512, out _); int bytesRead = Bc4.Decompress<ColorBGRA<byte>, byte>(data, 512, 512, out _);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
[Test] [Test]
public void DecompressBC5Test() public void Decompress_BC5()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5");
int bytesRead = Bc5.Decompress(data, 512, 512, out _); int bytesRead = Bc5.Decompress<ColorBGRA<byte>, byte>(data, 512, 512, out _);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
[TestCase("test.bc6h_fast")] [TestCase("test.bc6h_fast")]
[TestCase("test.bc6h_normal")] [TestCase("test.bc6h_normal")]
[TestCase("test.bc6h_best")] [TestCase("test.bc6h_best")]
public void DecompressBC6HTest(string fileName) public void Decompress_BC6H(string fileName)
{ {
AssertCorrectBC6HDecompression(TestFileFolders.BcTestFiles + fileName, 512, 512, false); AssertCorrectBC6HDecompression(TestFileFolders.BcTestFiles + fileName, 512, 512, false);
} }
@@ -92,28 +92,99 @@ namespace AssetRipper.TextureDecoder.Tests
[TestCase("test.bc7_fast")] [TestCase("test.bc7_fast")]
[TestCase("test.bc7_normal")] [TestCase("test.bc7_normal")]
[TestCase("test.bc7_best")] [TestCase("test.bc7_best")]
public void DecompressBC7Test(string fileName) public void Decompress_BC7(string fileName)
{ {
AssertCorrectBC7Decompression(TestFileFolders.BcTestFiles + fileName, 512, 512); AssertCorrectBC7Decompression(TestFileFolders.BcTestFiles + fileName, 512, 512);
} }
[Test] [Test]
[Ignore("Not implemented")] public void PartialBlock_BC1([Range(1, 4)] int width, [Range(1, 4)] int height)
public void BC7PartialBlockTest([Range(1, 4)] int width, [Range(1, 4)] int height)
{ {
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1").AsSpan()[..Bc1.BlockSize];
int bytesRead = Bc1.Decompress<ColorBGRA<byte>, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA<byte>>()));
}
}
[Test]
public void PartialBlock_BC2([Range(1, 4)] int width, [Range(1, 4)] int height)
{
using (Assert.EnterMultipleScope())
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3").AsSpan()[..Bc2.BlockSize];
int bytesRead = Bc2.Decompress<ColorBGRA<byte>, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA<byte>>()));
}
}
[Test]
public void PartialBlock_BC3([Range(1, 4)] int width, [Range(1, 4)] int height)
{
using (Assert.EnterMultipleScope())
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5").AsSpan()[..Bc3.BlockSize];
int bytesRead = Bc3.Decompress<ColorBGRA<byte>, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA<byte>>()));
}
}
[Test]
public void PartialBlock_BC4([Range(1, 4)] int width, [Range(1, 4)] int height)
{
using (Assert.EnterMultipleScope())
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4").AsSpan()[..Bc4.BlockSize];
int bytesRead = Bc4.Decompress<ColorBGRA<byte>, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA<byte>>()));
}
}
[Test]
public void PartialBlock_BC5([Range(1, 4)] int width, [Range(1, 4)] int height)
{
using (Assert.EnterMultipleScope())
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5").AsSpan()[..Bc5.BlockSize];
int bytesRead = Bc5.Decompress<ColorBGRA<byte>, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA<byte>>()));
}
}
[Test]
public void PartialBlock_BC6H([Range(1, 4)] int width, [Range(1, 4)] int height)
{
using (Assert.EnterMultipleScope())
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc6h_best").AsSpan()[..Bc6h.BlockSize];
int bytesRead = Bc6h.Decompress<ColorBGRA<byte>, 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<ColorBGRA<byte>>()));
}
}
[Test]
public void PartialBlock_BC7([Range(1, 4)] int width, [Range(1, 4)] int height)
{
using (Assert.EnterMultipleScope())
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc7_best").AsSpan()[..Bc7.BlockSize]; 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<ColorBGRA<byte>, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>())); Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA<byte>>()));
}); }
} }
private static void AssertCorrectBC6HDecompression(string path, int width, int height, bool isSigned) private static void AssertCorrectBC6HDecompression(string path, int width, int height, bool isSigned)
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(path); ReadOnlySpan<byte> data = File.ReadAllBytes(path);
int bytesRead = Bc6h.Decompress(data, width, height, isSigned, out byte[] decodedData); int bytesRead = Bc6h.Decompress<ColorBGRA<byte>, byte>(data, width, height, isSigned, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
ByteArrayDeviation.AssertMinimalDeviation(decodedData, originalBgra32LogoData, MaxMeanDeviationBc6h, MaxStandardDeviationBc6h); ByteArrayDeviation.AssertMinimalDeviation(decodedData, originalBgra32LogoData, MaxMeanDeviationBc6h, MaxStandardDeviationBc6h);
} }
@@ -121,7 +192,7 @@ namespace AssetRipper.TextureDecoder.Tests
private static void AssertCorrectBC7Decompression(string path, int width, int height) private static void AssertCorrectBC7Decompression(string path, int width, int height)
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(path); ReadOnlySpan<byte> data = File.ReadAllBytes(path);
int bytesRead = Bc7.Decompress(data, width, height, out byte[] decodedData); int bytesRead = Bc7.Decompress<ColorBGRA<byte>, byte>(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
ByteArrayDeviation.AssertMinimalDeviation(decodedData, originalBgra32LogoData, MaxMeanDeviationBc7, MaxStandardDeviationBc7); ByteArrayDeviation.AssertMinimalDeviation(decodedData, originalBgra32LogoData, MaxMeanDeviationBc7, MaxStandardDeviationBc7);
} }
@@ -30,11 +30,11 @@ internal static class ByteArrayDeviation
double standardDeviation = Math.Sqrt(sumOfSquaredDeviations / (decoded.Length - 1)); double standardDeviation = Math.Sqrt(sumOfSquaredDeviations / (decoded.Length - 1));
//Not sure if Bessel's correction is needed here, but it doesn't hurt, especially since length is around 1 million in the current use. //Not sure if Bessel's correction is needed here, but it doesn't hurt, especially since length is around 1 million in the current use.
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(mean, Is.LessThan(maxMeanDeviation), "Mean too far positive"); Assert.That(mean, Is.LessThan(maxMeanDeviation), "Mean too far positive");
Assert.That(mean, Is.GreaterThan(-maxMeanDeviation), "Mean too far negative"); Assert.That(mean, Is.GreaterThan(-maxMeanDeviation), "Mean too far negative");
Assert.That(standardDeviation, Is.LessThan(maxStandardDeviation), "Standard deviation too large"); Assert.That(standardDeviation, Is.LessThan(maxStandardDeviation), "Standard deviation too large");
}); }
} }
} }
+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 public sealed class DxtTests
{ {
@@ -9,7 +11,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps 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<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out _);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -22,7 +24,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps 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<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out _);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -35,7 +37,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps 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<ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out _);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
+14 -14
View File
@@ -6,14 +6,14 @@ namespace AssetRipper.TextureDecoder.Tests;
public sealed class EtcTests 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 ETCDelegate { get; } = EtcDecoder.DecompressETC<ColorBGRA<byte>, byte>;
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 ETC2Delegate { get; } = EtcDecoder.DecompressETC2<ColorBGRA<byte>, byte>;
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 ETC2A1Delegate { get; } = EtcDecoder.DecompressETC2A1<ColorBGRA<byte>, byte>;
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 ETC2A8Delegate { get; } = EtcDecoder.DecompressETC2A8<ColorBGRA<byte>, byte>;
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 EACRSignedDelegate { get; } = EtcDecoder.DecompressEACRSigned<ColorBGRA<byte>, byte>;
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 EACRUnsignedDelegate { get; } = EtcDecoder.DecompressEACRUnsigned<ColorBGRA<byte>, byte>;
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 EACRGSignedDelegate { get; } = EtcDecoder.DecompressEACRGSigned<ColorBGRA<byte>, byte>;
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 EACRGUnsignedDelegate { get; } = EtcDecoder.DecompressEACRGUnsigned<ColorBGRA<byte>, byte>;
[Test] [Test]
public void DecompressETCTest() => AssertCorrectByteCountReadFor256SquareWithMips(TestFileFolders.EtcTestFiles + "test.etc", ETCDelegate); public void DecompressETCTest() => AssertCorrectByteCountReadFor256SquareWithMips(TestFileFolders.EtcTestFiles + "test.etc", ETCDelegate);
@@ -57,7 +57,7 @@ public sealed class EtcTests
[Test] [Test]
public void CorrectnessEACRGUnsignedTest() => AssertCorrectDecompression<AndroidTextures.Logo_15, ColorRG<byte>>(EACRGUnsignedDelegate, .1, .6); 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) private static void AssertCorrectByteCountReadFor256SquareWithMips(string path, DecodingDelegate decoder)
{ {
@@ -65,7 +65,7 @@ public sealed class EtcTests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps 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; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); 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> 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; 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) if (!TTexture.Mips)
{ {
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
@@ -88,12 +88,12 @@ public sealed class EtcTests
byte[] comparisonData = AndroidTextures.Logo_00.Data; byte[] comparisonData = AndroidTextures.Logo_00.Data;
//Remove unused color channels //Remove unused color channels
if (typeof(TColor) != typeof(ColorBGRA32) && typeof(TColor) != typeof(ColorRGBA<byte>)) if (typeof(TColor) != typeof(ColorBGRA<byte>) && typeof(TColor) != typeof(ColorRGBA<byte>))
{ {
Span<ColorBGRA32> pixels = MemoryMarshal.Cast<byte, ColorBGRA32>((Span<byte>)comparisonData); Span<ColorBGRA<byte>> pixels = MemoryMarshal.Cast<byte, ColorBGRA<byte>>((Span<byte>)comparisonData);
for (int i = 0; i < pixels.Length; i++) for (int i = 0; i < pixels.Length; i++)
{ {
pixels[i] = pixels[i].Convert<ColorBGRA32, byte, TColor, byte>().Convert<TColor, byte, ColorBGRA32, byte>(); pixels[i] = pixels[i].Convert<ColorBGRA<byte>, byte, TColor, byte>().Convert<TColor, byte, ColorBGRA<byte>, byte>();
} }
} }
@@ -57,12 +57,12 @@ public partial class ColorARGB16Tests
var b = color.B; var b = color.B;
var a = color.A; var a = color.A;
color.R = MakeRandomValue(); color.R = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -73,12 +73,12 @@ public partial class ColorARGB16Tests
var b = color.B; var b = color.B;
var a = color.A; var a = color.A;
color.G = MakeRandomValue(); color.G = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -89,12 +89,12 @@ public partial class ColorARGB16Tests
var g = color.G; var g = color.G;
var a = color.A; var a = color.A;
color.B = MakeRandomValue(); color.B = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -105,12 +105,12 @@ public partial class ColorARGB16Tests
var g = color.G; var g = color.G;
var b = color.B; var b = color.B;
color.A = MakeRandomValue(); color.A = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
}); };
} }
[Test] [Test]
@@ -118,13 +118,13 @@ public partial class ColorARGB16Tests
{ {
var color = MakeRandomColor(); var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a); color.GetChannels(out var r, out var g, out var b, out var a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -133,31 +133,19 @@ public partial class ColorARGB16Tests
var color = MakeRandomColor(); var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a); color.GetChannels(out var r, out var g, out var b, out var a);
color.SetChannels(r, g, b, a); color.SetChannels(r, g, b, a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
public static ColorARGB16 MakeRandomColor() => ColorRandom<ColorARGB16, byte>.MakeRandomColor(); public static ColorARGB16 MakeRandomColor() => ColorRandom<ColorARGB16, byte>.MakeRandomColor();
public static byte MakeRandomValue() => ColorRandom<ColorARGB16, byte>.MakeRandomValue(); public static byte MakeRandomValue() => ColorRandom<ColorARGB16, byte>.MakeRandomValue();
[Test]
public void ConversionIsLosslessToColorARGB32()
{
LosslessConversion.Assert<ColorARGB16, byte, ColorARGB32, byte>();
}
[Test]
public void ConversionIsLosslessToColorBGRA32()
{
LosslessConversion.Assert<ColorARGB16, byte, ColorBGRA32, byte>();
}
[Test] [Test]
public void ConversionIsLosslessToColorRGBA_sbyte() public void ConversionIsLosslessToColorRGBA_sbyte()
{ {
@@ -1,232 +0,0 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Formats;
using System.Runtime.CompilerServices;
namespace AssetRipper.TextureDecoder.Tests.Formats;
public partial class ColorARGB32Tests
{
[Test]
public void CorrectSizeTest()
{
Assert.That(Unsafe.SizeOf<ColorARGB32>(), Is.EqualTo(4));
}
[Test]
public void PropertyIsSymmetric_R()
{
var color = MakeRandomColor();
var r = color.R;
color.R = r;
Assert.That(color.R, Is.EqualTo(r));
}
[Test]
public void PropertyIsSymmetric_G()
{
var color = MakeRandomColor();
var g = color.G;
color.G = g;
Assert.That(color.G, Is.EqualTo(g));
}
[Test]
public void PropertyIsSymmetric_B()
{
var color = MakeRandomColor();
var b = color.B;
color.B = b;
Assert.That(color.B, Is.EqualTo(b));
}
[Test]
public void PropertyIsSymmetric_A()
{
var color = MakeRandomColor();
var a = color.A;
color.A = a;
Assert.That(color.A, Is.EqualTo(a));
}
[Test]
public void ChannelsAreIndependent_R()
{
var color = MakeRandomColor();
var g = color.G;
var b = color.B;
var a = color.A;
color.R = MakeRandomValue();
Assert.Multiple(() =>
{
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a));
});
}
[Test]
public void ChannelsAreIndependent_G()
{
var color = MakeRandomColor();
var r = color.R;
var b = color.B;
var a = color.A;
color.G = MakeRandomValue();
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a));
});
}
[Test]
public void ChannelsAreIndependent_B()
{
var color = MakeRandomColor();
var r = color.R;
var g = color.G;
var a = color.A;
color.B = MakeRandomValue();
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.A, Is.EqualTo(a));
});
}
[Test]
public void ChannelsAreIndependent_A()
{
var color = MakeRandomColor();
var r = color.R;
var g = color.G;
var b = color.B;
color.A = MakeRandomValue();
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b));
});
}
[Test]
public void GetMethodMatchesProperties()
{
var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a);
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a));
});
}
[Test]
public void MethodsAreSymmetric()
{
var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a);
color.SetChannels(r, g, b, a);
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a));
});
}
public static ColorARGB32 MakeRandomColor() => ColorRandom<ColorARGB32, byte>.MakeRandomColor();
public static byte MakeRandomValue() => ColorRandom<ColorARGB32, byte>.MakeRandomValue();
[Test]
public void ConversionIsLosslessToColorARGB32()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorARGB32, byte>();
}
[Test]
public void ConversionIsLosslessToColorBGRA32()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorBGRA32, byte>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_sbyte()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<sbyte>, sbyte>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_byte()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<byte>, byte>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_short()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<short>, short>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_ushort()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<ushort>, ushort>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_int()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<int>, int>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_uint()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<uint>, uint>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_nint()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<nint>, nint>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_nuint()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<nuint>, nuint>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_long()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<long>, long>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_ulong()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<ulong>, ulong>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_Int128()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<Int128>, Int128>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_UInt128()
{
LosslessConversion.Assert<ColorARGB32, byte, ColorRGBA<UInt128>, UInt128>();
}
}
@@ -1,232 +0,0 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Formats;
using System.Runtime.CompilerServices;
namespace AssetRipper.TextureDecoder.Tests.Formats;
public partial class ColorBGRA32Tests
{
[Test]
public void CorrectSizeTest()
{
Assert.That(Unsafe.SizeOf<ColorBGRA32>(), Is.EqualTo(4));
}
[Test]
public void PropertyIsSymmetric_R()
{
var color = MakeRandomColor();
var r = color.R;
color.R = r;
Assert.That(color.R, Is.EqualTo(r));
}
[Test]
public void PropertyIsSymmetric_G()
{
var color = MakeRandomColor();
var g = color.G;
color.G = g;
Assert.That(color.G, Is.EqualTo(g));
}
[Test]
public void PropertyIsSymmetric_B()
{
var color = MakeRandomColor();
var b = color.B;
color.B = b;
Assert.That(color.B, Is.EqualTo(b));
}
[Test]
public void PropertyIsSymmetric_A()
{
var color = MakeRandomColor();
var a = color.A;
color.A = a;
Assert.That(color.A, Is.EqualTo(a));
}
[Test]
public void ChannelsAreIndependent_R()
{
var color = MakeRandomColor();
var g = color.G;
var b = color.B;
var a = color.A;
color.R = MakeRandomValue();
Assert.Multiple(() =>
{
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a));
});
}
[Test]
public void ChannelsAreIndependent_G()
{
var color = MakeRandomColor();
var r = color.R;
var b = color.B;
var a = color.A;
color.G = MakeRandomValue();
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a));
});
}
[Test]
public void ChannelsAreIndependent_B()
{
var color = MakeRandomColor();
var r = color.R;
var g = color.G;
var a = color.A;
color.B = MakeRandomValue();
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.A, Is.EqualTo(a));
});
}
[Test]
public void ChannelsAreIndependent_A()
{
var color = MakeRandomColor();
var r = color.R;
var g = color.G;
var b = color.B;
color.A = MakeRandomValue();
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b));
});
}
[Test]
public void GetMethodMatchesProperties()
{
var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a);
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a));
});
}
[Test]
public void MethodsAreSymmetric()
{
var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a);
color.SetChannels(r, g, b, a);
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a));
});
}
public static ColorBGRA32 MakeRandomColor() => ColorRandom<ColorBGRA32, byte>.MakeRandomColor();
public static byte MakeRandomValue() => ColorRandom<ColorBGRA32, byte>.MakeRandomValue();
[Test]
public void ConversionIsLosslessToColorARGB32()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorARGB32, byte>();
}
[Test]
public void ConversionIsLosslessToColorBGRA32()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorBGRA32, byte>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_sbyte()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<sbyte>, sbyte>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_byte()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<byte>, byte>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_short()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<short>, short>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_ushort()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<ushort>, ushort>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_int()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<int>, int>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_uint()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<uint>, uint>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_nint()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<nint>, nint>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_nuint()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<nuint>, nuint>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_long()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<long>, long>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_ulong()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<ulong>, ulong>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_Int128()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<Int128>, Int128>();
}
[Test]
public void ConversionIsLosslessToColorRGBA_UInt128()
{
LosslessConversion.Assert<ColorBGRA32, byte, ColorRGBA<UInt128>, UInt128>();
}
}
@@ -57,12 +57,12 @@ public partial class ColorRGB16Tests
var b = color.B; var b = color.B;
var a = color.A; var a = color.A;
color.R = MakeRandomValue(); color.R = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -73,12 +73,12 @@ public partial class ColorRGB16Tests
var b = color.B; var b = color.B;
var a = color.A; var a = color.A;
color.G = MakeRandomValue(); color.G = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -89,12 +89,12 @@ public partial class ColorRGB16Tests
var g = color.G; var g = color.G;
var a = color.A; var a = color.A;
color.B = MakeRandomValue(); color.B = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -105,12 +105,12 @@ public partial class ColorRGB16Tests
var g = color.G; var g = color.G;
var b = color.B; var b = color.B;
color.A = MakeRandomValue(); color.A = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
}); };
} }
[Test] [Test]
@@ -118,13 +118,13 @@ public partial class ColorRGB16Tests
{ {
var color = MakeRandomColor(); var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a); color.GetChannels(out var r, out var g, out var b, out var a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -133,31 +133,19 @@ public partial class ColorRGB16Tests
var color = MakeRandomColor(); var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a); color.GetChannels(out var r, out var g, out var b, out var a);
color.SetChannels(r, g, b, a); color.SetChannels(r, g, b, a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
public static ColorRGB16 MakeRandomColor() => ColorRandom<ColorRGB16, byte>.MakeRandomColor(); public static ColorRGB16 MakeRandomColor() => ColorRandom<ColorRGB16, byte>.MakeRandomColor();
public static byte MakeRandomValue() => ColorRandom<ColorRGB16, byte>.MakeRandomValue(); public static byte MakeRandomValue() => ColorRandom<ColorRGB16, byte>.MakeRandomValue();
[Test]
public void ConversionIsLosslessToColorARGB32()
{
LosslessConversion.Assert<ColorRGB16, byte, ColorARGB32, byte>();
}
[Test]
public void ConversionIsLosslessToColorBGRA32()
{
LosslessConversion.Assert<ColorRGB16, byte, ColorBGRA32, byte>();
}
[Test] [Test]
public void ConversionIsLosslessToColorRGBA_sbyte() public void ConversionIsLosslessToColorRGBA_sbyte()
{ {
@@ -18,13 +18,13 @@ public partial class ColorRGB9e5Tests
{ {
var color = MakeRandomColor(); var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a); color.GetChannels(out var r, out var g, out var b, out var a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -33,13 +33,13 @@ public partial class ColorRGB9e5Tests
var color = MakeRandomColor(); var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a); color.GetChannels(out var r, out var g, out var b, out var a);
color.SetChannels(r, g, b, a); color.SetChannels(r, g, b, a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
public static ColorRGB9e5 MakeRandomColor() => ColorRandom<ColorRGB9e5, double>.MakeRandomColor(); public static ColorRGB9e5 MakeRandomColor() => ColorRandom<ColorRGB9e5, double>.MakeRandomColor();
@@ -57,12 +57,12 @@ public partial class ColorRGBA16Tests
var b = color.B; var b = color.B;
var a = color.A; var a = color.A;
color.R = MakeRandomValue(); color.R = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -73,12 +73,12 @@ public partial class ColorRGBA16Tests
var b = color.B; var b = color.B;
var a = color.A; var a = color.A;
color.G = MakeRandomValue(); color.G = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -89,12 +89,12 @@ public partial class ColorRGBA16Tests
var g = color.G; var g = color.G;
var a = color.A; var a = color.A;
color.B = MakeRandomValue(); color.B = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -105,12 +105,12 @@ public partial class ColorRGBA16Tests
var g = color.G; var g = color.G;
var b = color.B; var b = color.B;
color.A = MakeRandomValue(); color.A = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
}); };
} }
[Test] [Test]
@@ -118,13 +118,13 @@ public partial class ColorRGBA16Tests
{ {
var color = MakeRandomColor(); var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a); color.GetChannels(out var r, out var g, out var b, out var a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
[Test] [Test]
@@ -133,31 +133,19 @@ public partial class ColorRGBA16Tests
var color = MakeRandomColor(); var color = MakeRandomColor();
color.GetChannels(out var r, out var g, out var b, out var a); color.GetChannels(out var r, out var g, out var b, out var a);
color.SetChannels(r, g, b, a); color.SetChannels(r, g, b, a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); };
} }
public static ColorRGBA16 MakeRandomColor() => ColorRandom<ColorRGBA16, byte>.MakeRandomColor(); public static ColorRGBA16 MakeRandomColor() => ColorRandom<ColorRGBA16, byte>.MakeRandomColor();
public static byte MakeRandomValue() => ColorRandom<ColorRGBA16, byte>.MakeRandomValue(); public static byte MakeRandomValue() => ColorRandom<ColorRGBA16, byte>.MakeRandomValue();
[Test]
public void ConversionIsLosslessToColorARGB32()
{
LosslessConversion.Assert<ColorRGBA16, byte, ColorARGB32, byte>();
}
[Test]
public void ConversionIsLosslessToColorBGRA32()
{
LosslessConversion.Assert<ColorRGBA16, byte, ColorBGRA32, byte>();
}
[Test] [Test]
public void ConversionIsLosslessToColorRGBA_sbyte() public void ConversionIsLosslessToColorRGBA_sbyte()
{ {
@@ -55,12 +55,12 @@ internal partial class GenericColorTests<TColor, TChannelValue> where TColor : u
TChannelValue b = color.B; TChannelValue b = color.B;
TChannelValue a = color.A; TChannelValue a = color.A;
color.R = MakeRandomValue(); color.R = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); }
} }
[Test] [Test]
@@ -71,12 +71,12 @@ internal partial class GenericColorTests<TColor, TChannelValue> where TColor : u
TChannelValue b = color.B; TChannelValue b = color.B;
TChannelValue a = color.A; TChannelValue a = color.A;
color.G = MakeRandomValue(); color.G = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); }
} }
[Test] [Test]
@@ -87,12 +87,12 @@ internal partial class GenericColorTests<TColor, TChannelValue> where TColor : u
TChannelValue g = color.G; TChannelValue g = color.G;
TChannelValue a = color.A; TChannelValue a = color.A;
color.B = MakeRandomValue(); color.B = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); }
} }
[Test] [Test]
@@ -103,12 +103,12 @@ internal partial class GenericColorTests<TColor, TChannelValue> where TColor : u
TChannelValue g = color.G; TChannelValue g = color.G;
TChannelValue b = color.B; TChannelValue b = color.B;
color.A = MakeRandomValue(); color.A = MakeRandomValue();
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
}); }
} }
[Test] [Test]
@@ -116,13 +116,13 @@ internal partial class GenericColorTests<TColor, TChannelValue> where TColor : u
{ {
TColor color = MakeRandomColor(); TColor color = MakeRandomColor();
color.GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a); color.GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); }
} }
[Test] [Test]
@@ -131,13 +131,13 @@ internal partial class GenericColorTests<TColor, TChannelValue> where TColor : u
TColor color = MakeRandomColor(); TColor color = MakeRandomColor();
color.GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a); color.GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a);
color.SetChannels(r, g, b, a); color.SetChannels(r, g, b, a);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(color.R, Is.EqualTo(r)); Assert.That(color.R, Is.EqualTo(r));
Assert.That(color.G, Is.EqualTo(g)); Assert.That(color.G, Is.EqualTo(g));
Assert.That(color.B, Is.EqualTo(b)); Assert.That(color.B, Is.EqualTo(b));
Assert.That(color.A, Is.EqualTo(a)); Assert.That(color.A, Is.EqualTo(a));
}); }
} }
private static TChannelValue MakeRandomValue() => ColorRandom<TColor, TChannelValue>.MakeRandomValue(); private static TChannelValue MakeRandomValue() => ColorRandom<TColor, TChannelValue>.MakeRandomValue();
@@ -8,86 +8,120 @@ namespace AssetRipper.TextureDecoder.Tests.Formats.Generic;
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<sbyte>), typeof(sbyte) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<sbyte>), typeof(sbyte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<sbyte>), typeof(sbyte) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<sbyte>), typeof(sbyte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<sbyte>), typeof(sbyte) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<sbyte>), typeof(sbyte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<sbyte>), typeof(sbyte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<sbyte>), typeof(sbyte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<byte>), typeof(byte) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<byte>), typeof(byte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<byte>), typeof(byte) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<byte>), typeof(byte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<byte>), typeof(byte) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<byte>), typeof(byte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<byte>), typeof(byte) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<byte>), typeof(byte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<byte>), typeof(byte) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<byte>), typeof(byte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<byte>), typeof(byte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<byte>), typeof(byte) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<short>), typeof(short) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<short>), typeof(short) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<short>), typeof(short) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<short>), typeof(short) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<short>), typeof(short) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<short>), typeof(short) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<short>), typeof(short) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<short>), typeof(short) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<short>), typeof(short) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<short>), typeof(short) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<short>), typeof(short) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<short>), typeof(short) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<ushort>), typeof(ushort) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<ushort>), typeof(ushort) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<ushort>), typeof(ushort) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<ushort>), typeof(ushort) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<ushort>), typeof(ushort) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<ushort>), typeof(ushort) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<ushort>), typeof(ushort) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<ushort>), typeof(ushort) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<ushort>), typeof(ushort) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<ushort>), typeof(ushort) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<ushort>), typeof(ushort) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<ushort>), typeof(ushort) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<int>), typeof(int) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<int>), typeof(int) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<int>), typeof(int) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<int>), typeof(int) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<int>), typeof(int) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<int>), typeof(int) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<int>), typeof(int) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<int>), typeof(int) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<int>), typeof(int) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<int>), typeof(int) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<int>), typeof(int) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<int>), typeof(int) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<uint>), typeof(uint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<uint>), typeof(uint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<uint>), typeof(uint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<uint>), typeof(uint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<uint>), typeof(uint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<uint>), typeof(uint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<uint>), typeof(uint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<uint>), typeof(uint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<uint>), typeof(uint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<uint>), typeof(uint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<uint>), typeof(uint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<uint>), typeof(uint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<nint>), typeof(nint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<nint>), typeof(nint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<nint>), typeof(nint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<nint>), typeof(nint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<nint>), typeof(nint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<nint>), typeof(nint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<nint>), typeof(nint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<nint>), typeof(nint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<nint>), typeof(nint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<nint>), typeof(nint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<nint>), typeof(nint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<nint>), typeof(nint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<nuint>), typeof(nuint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<nuint>), typeof(nuint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<nuint>), typeof(nuint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<nuint>), typeof(nuint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<nuint>), typeof(nuint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<nuint>), typeof(nuint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<nuint>), typeof(nuint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<nuint>), typeof(nuint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<nuint>), typeof(nuint) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<nuint>), typeof(nuint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<nuint>), typeof(nuint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<nuint>), typeof(nuint) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<long>), typeof(long) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<long>), typeof(long) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<long>), typeof(long) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<long>), typeof(long) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<long>), typeof(long) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<long>), typeof(long) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<long>), typeof(long) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<long>), typeof(long) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<long>), typeof(long) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<long>), typeof(long) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<long>), typeof(long) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<long>), typeof(long) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<ulong>), typeof(ulong) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<ulong>), typeof(ulong) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<ulong>), typeof(ulong) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<ulong>), typeof(ulong) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<ulong>), typeof(ulong) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<ulong>), typeof(ulong) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<ulong>), typeof(ulong) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<ulong>), typeof(ulong) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<ulong>), typeof(ulong) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<ulong>), typeof(ulong) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<ulong>), typeof(ulong) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<ulong>), typeof(ulong) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<Int128>), typeof(Int128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<Int128>), typeof(Int128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<Int128>), typeof(Int128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<Int128>), typeof(Int128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<Int128>), typeof(Int128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<Int128>), typeof(Int128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<Int128>), typeof(Int128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<Int128>), typeof(Int128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<Int128>), typeof(Int128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<Int128>), typeof(Int128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<Int128>), typeof(Int128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<Int128>), typeof(Int128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<UInt128>), typeof(UInt128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<UInt128>), typeof(UInt128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<UInt128>), typeof(UInt128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<UInt128>), typeof(UInt128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<UInt128>), typeof(UInt128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<UInt128>), typeof(UInt128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<UInt128>), typeof(UInt128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<UInt128>), typeof(UInt128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<UInt128>), typeof(UInt128) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<UInt128>), typeof(UInt128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<UInt128>), typeof(UInt128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<UInt128>), typeof(UInt128) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<Half>), typeof(Half) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<Half>), typeof(Half) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<Half>), typeof(Half) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<Half>), typeof(Half) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<Half>), typeof(Half) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<Half>), typeof(Half) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<Half>), typeof(Half) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<Half>), typeof(Half) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<Half>), typeof(Half) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<Half>), typeof(Half) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<Half>), typeof(Half) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<Half>), typeof(Half) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<float>), typeof(float) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<float>), typeof(float) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<float>), typeof(float) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<float>), typeof(float) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<float>), typeof(float) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<float>), typeof(float) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<float>), typeof(float) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<float>), typeof(float) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<float>), typeof(float) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<float>), typeof(float) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<float>), typeof(float) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<float>), typeof(float) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<NFloat>), typeof(NFloat) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<NFloat>), typeof(NFloat) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<NFloat>), typeof(NFloat) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<NFloat>), typeof(NFloat) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<NFloat>), typeof(NFloat) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<NFloat>), typeof(NFloat) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<NFloat>), typeof(NFloat) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<NFloat>), typeof(NFloat) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<NFloat>), typeof(NFloat) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<NFloat>), typeof(NFloat) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<NFloat>), typeof(NFloat) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<NFloat>), typeof(NFloat) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<double>), typeof(double) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<double>), typeof(double) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<double>), typeof(double) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<double>), typeof(double) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<double>), typeof(double) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<double>), typeof(double) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<double>), typeof(double) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<double>), typeof(double) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<double>), typeof(double) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<double>), typeof(double) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<double>), typeof(double) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<double>), typeof(double) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorR<decimal>), typeof(decimal) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorR<decimal>), typeof(decimal) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRG<decimal>), typeof(decimal) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRG<decimal>), typeof(decimal) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<decimal>), typeof(decimal) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<decimal>), typeof(decimal) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<decimal>), typeof(decimal) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<decimal>), typeof(decimal) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<decimal>), typeof(decimal) })] [TestFixture(TypeArgs = new Type[] { typeof(ColorA<decimal>), typeof(decimal) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorARGB<decimal>), typeof(decimal) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorBGRA<decimal>), typeof(decimal) })]
partial class GenericColorTests<TColor, TChannelValue> partial class GenericColorTests<TColor, TChannelValue>
{ {
} }
@@ -9,26 +9,26 @@ namespace AssetRipper.TextureDecoder.Tests.Formats.Generic;
[TestFixture(TypeArgs = [typeof(long), typeof(ulong)])] [TestFixture(TypeArgs = [typeof(long), typeof(ulong)])]
[TestFixture(TypeArgs = [typeof(Int128), typeof(UInt128)])] [TestFixture(TypeArgs = [typeof(Int128), typeof(UInt128)])]
internal class NumericConversionExtremesTests<TSigned, TUnsigned> internal class NumericConversionExtremesTests<TSigned, TUnsigned>
where TSigned : unmanaged, IMinMaxValue<TSigned> where TSigned : unmanaged, IMinMaxValue<TSigned>
where TUnsigned : unmanaged, IMinMaxValue<TUnsigned> where TUnsigned : unmanaged, IMinMaxValue<TUnsigned>
{ {
[Test] [Test]
public void SignedToUnsignedExtremesAreCorrect() public void SignedToUnsignedExtremesAreCorrect()
{ {
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(NumericConversion.Convert<TSigned, TUnsigned>(TSigned.MinValue), Is.EqualTo(TUnsigned.MinValue)); Assert.That(NumericConversion.Convert<TSigned, TUnsigned>(TSigned.MinValue), Is.EqualTo(TUnsigned.MinValue));
Assert.That(NumericConversion.Convert<TSigned, TUnsigned>(TSigned.MaxValue), Is.EqualTo(TUnsigned.MaxValue)); Assert.That(NumericConversion.Convert<TSigned, TUnsigned>(TSigned.MaxValue), Is.EqualTo(TUnsigned.MaxValue));
}); }
} }
[Test] [Test]
public void UnsignedToSignedExtremesAreCorrect() public void UnsignedToSignedExtremesAreCorrect()
{ {
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(NumericConversion.Convert<TUnsigned, TSigned>(TUnsigned.MinValue), Is.EqualTo(TSigned.MinValue)); Assert.That(NumericConversion.Convert<TUnsigned, TSigned>(TUnsigned.MinValue), Is.EqualTo(TSigned.MinValue));
Assert.That(NumericConversion.Convert<TUnsigned, TSigned>(TUnsigned.MaxValue), Is.EqualTo(TSigned.MaxValue)); Assert.That(NumericConversion.Convert<TUnsigned, TSigned>(TUnsigned.MaxValue), Is.EqualTo(TSigned.MaxValue));
}); }
} }
} }
@@ -0,0 +1,512 @@
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
namespace AssetRipper.TextureDecoder.Tests;
internal static class OldRgbConverter
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int A8ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return A8ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int A8ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = 0; // b
output[oo + 1] = 0; // g
output[oo + 2] = 0; // r
output[oo + 3] = input[io]; // a
oo += 4;
io++;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB24ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGB24ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB24ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 2]; // b
output[oo + 1] = input[io + 1]; // g
output[oo + 2] = input[io + 0]; // r
output[oo + 3] = 255; // a
io += 3;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBA32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGBA32ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBA32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 2]; // b
output[oo + 1] = input[io + 1]; // g
output[oo + 2] = input[io + 0]; // r
output[oo + 3] = input[io + 3]; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int ARGB32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return ARGB32ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int ARGB32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 3]; // b
output[oo + 1] = input[io + 2]; // g
output[oo + 2] = input[io + 1]; // r
output[oo + 3] = input[io + 0]; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB16ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGB16ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB16ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = (byte)(input[io + 1] & 0xF8);
byte g = unchecked((byte)((input[io + 1] << 5) | ((input[io + 0] & 0xE0) >> 3)));
byte b = unchecked((byte)(input[io + 0] << 3));
output[oo + 0] = b; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 2;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RG16ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RG16ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RG16ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = 0; // b
output[oo + 1] = input[io + 1]; // g
output[oo + 2] = input[io + 0]; // r
output[oo + 3] = 255; // a
io += 2;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int R8ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return R8ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int R8ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = 0; // b
output[oo + 1] = 0; // g
output[oo + 2] = input[io + 0]; // r
output[oo + 3] = 255; // a
io += 1;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RHalfToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToHalf(input, io) * 255f);
output[oo + 0] = 0; // b
output[oo + 1] = 0; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 2;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGHalfToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToHalf(input, io + 0) * 255f);
byte g = ClampByte(ToHalf(input, io + 2) * 255f);
output[oo + 0] = 0; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBAHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGBAHalfToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBAHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToHalf(input, io + 0) * 255f);
byte g = ClampByte(ToHalf(input, io + 2) * 255f);
byte b = ClampByte(ToHalf(input, io + 4) * 255f);
byte a = ClampByte(ToHalf(input, io + 6) * 255f);
output[oo + 0] = b; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = a; // a
io += 8;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RFloatToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToSingle(input, io) * 255f);
output[oo + 0] = 0; // b
output[oo + 1] = 0; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGFloatToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToSingle(input, io + 0) * 255f);
byte g = ClampByte(ToSingle(input, io + 4) * 255f);
output[oo + 0] = 0; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 8;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBAFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGBAFloatToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBAFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToSingle(input, io + 0) * 255f);
byte g = ClampByte(ToSingle(input, io + 4) * 255f);
byte b = ClampByte(ToSingle(input, io + 8) * 255f);
byte a = ClampByte(ToSingle(input, io + 12) * 255f);
output[oo + 0] = b; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = a; // a
io += 16;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB9e5FloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGB9e5FloatToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB9e5FloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
uint value = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(io, 4));
double scale = Math.Pow(2, unchecked((int)(value >> 27) - 24));
byte r = ClampByte((value >> 0 & 0x1FF) * scale * 255.0);
byte g = ClampByte((value >> 9 & 0x1FF) * scale * 255.0);
byte b = ClampByte((value >> 18 & 0x1FF) * scale * 255.0);
output[oo + 0] = b; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RG32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RG32ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RG32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = 0; // b
output[oo + 1] = input[io + 3]; // g
output[oo + 2] = input[io + 1]; // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB48ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGB48ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB48ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 5]; // b
output[oo + 1] = input[io + 3]; // g
output[oo + 2] = input[io + 1]; // r
output[oo + 3] = 255; // a
io += 6;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBA64ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGBA64ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBA64ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 5]; // b
output[oo + 1] = input[io + 3]; // g
output[oo + 2] = input[io + 1]; // r
output[oo + 3] = input[io + 7]; // a
io += 8;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static float ToHalf(ReadOnlySpan<byte> input, int offset)
{
return (float)BinaryPrimitives.ReadHalfLittleEndian(input.Slice(offset, 2));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static float ToSingle(ReadOnlySpan<byte> input, int offset)
{
return BinaryPrimitives.ReadSingleLittleEndian(input.Slice(offset, 4));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static byte ClampByte(float x)
{
return byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static byte ClampByte(double x)
{
return byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue);
}
}
@@ -1,4 +1,5 @@
using AssetRipper.TextureDecoder.Pvrtc; using AssetRipper.TextureDecoder.Pvrtc;
using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Tests; namespace AssetRipper.TextureDecoder.Tests;
@@ -11,7 +12,7 @@ public sealed class PvrtcTests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 512, 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps 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<ColorBGRA<byte>, byte>(data, size, size, false, out _);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); 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 private static void AssertCorrectDecompression<T>(bool do2bitMode, double maxMeanDeviation, double maxStandardDeviation) where T : ITexture
{ {
ReadOnlySpan<byte> data = T.Data; ReadOnlySpan<byte> data = T.Data;
int bytesRead = PvrtcDecoder.DecompressPVRTC(data, T.Width, T.Height, do2bitMode, out byte[] decompressedData); int bytesRead = PvrtcDecoder.DecompressPVRTC<ColorBGRA<byte>, byte>(data, T.Width, T.Height, do2bitMode, out byte[] decompressedData);
if (!T.Mips) if (!T.Mips)
{ {
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
@@ -3,7 +3,7 @@ using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Tests namespace AssetRipper.TextureDecoder.Tests
{ {
public sealed class NewRgbTests public sealed class RgbConsistencyTests
{ {
[Test] [Test]
public void ConvertA8Test() public void ConvertA8Test()
@@ -12,8 +12,8 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.A8ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData); int bytesRead = OldRgbConverter.A8ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData);
RgbConverter.Convert<ColorA<byte>, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData); RgbConverter.Convert<ColorA<byte>, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -27,7 +27,7 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.Convert<ColorARGB16, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out _); int bytesRead = RgbConverter.Convert<ColorARGB16, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out _);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -40,8 +40,8 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.RGB24ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData); int bytesRead = OldRgbConverter.RGB24ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRGB<byte>, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData); RgbConverter.Convert<ColorRGB<byte>, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -55,8 +55,8 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.RGBA32ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData); int bytesRead = OldRgbConverter.RGBA32ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData); RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -70,8 +70,8 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.ARGB32ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData); int bytesRead = OldRgbConverter.ARGB32ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData);
RgbConverter.Convert<ColorARGB32, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData); RgbConverter.Convert<ColorARGB<byte>, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -85,8 +85,8 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.RGB16ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData); int bytesRead = OldRgbConverter.RGB16ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRGB16, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData); RgbConverter.Convert<ColorRGB16, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -100,7 +100,7 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.Convert<ColorR<ushort>, ushort, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out _); int bytesRead = RgbConverter.Convert<ColorR<ushort>, ushort, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out _);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -113,7 +113,7 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.Convert<ColorRGBA16, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out _); int bytesRead = RgbConverter.Convert<ColorRGBA16, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out _);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -126,8 +126,8 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.RG16ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData); int bytesRead = OldRgbConverter.RG16ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData); RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -141,8 +141,8 @@ namespace AssetRipper.TextureDecoder.Tests
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = RgbConverter.R8ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData); int bytesRead = OldRgbConverter.R8ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData);
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData); RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA<byte>, byte>(data.Slice(totalBytesRead), size, size, out byte[] newDecodedData);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -153,8 +153,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRHalfTest() public void ConvertRHalfTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rhalf"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rhalf");
RgbConverter.RHalfToBGRA32(data, 256, 256, out byte[] originalDecodedData); OldRgbConverter.RHalfToBGRA32(data, 256, 256, out byte[] originalDecodedData);
RgbConverter.Convert<ColorR<Half>, Half, ColorBGRA32, byte>(data, 256, 256, out byte[] newDecodedData); RgbConverter.Convert<ColorR<Half>, Half, ColorBGRA<byte>, byte>(data, 256, 256, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -162,8 +162,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRGHalfTest() public void ConvertRGHalfTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rghalf"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rghalf");
RgbConverter.RGHalfToBGRA32(data, 256, 256, out byte[] originalDecodedData); OldRgbConverter.RGHalfToBGRA32(data, 256, 256, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRG<Half>, Half, ColorBGRA32, byte>(data, 256, 256, out byte[] newDecodedData); RgbConverter.Convert<ColorRG<Half>, Half, ColorBGRA<byte>, byte>(data, 256, 256, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -171,8 +171,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRGBAHalfTest() public void ConvertRGBAHalfTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgbahalf"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgbahalf");
RgbConverter.RGBAHalfToBGRA32(data, 256, 256, out byte[] originalDecodedData); OldRgbConverter.RGBAHalfToBGRA32(data, 256, 256, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRGBA<Half>, Half, ColorBGRA32, byte>(data, 256, 256, out byte[] newDecodedData); RgbConverter.Convert<ColorRGBA<Half>, Half, ColorBGRA<byte>, byte>(data, 256, 256, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -180,8 +180,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRFloatTest() public void ConvertRFloatTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rfloat"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rfloat");
RgbConverter.RFloatToBGRA32(data, 256, 256, out byte[] originalDecodedData); OldRgbConverter.RFloatToBGRA32(data, 256, 256, out byte[] originalDecodedData);
RgbConverter.Convert<ColorR<float>, float, ColorBGRA32, byte>(data, 256, 256, out byte[] newDecodedData); RgbConverter.Convert<ColorR<float>, float, ColorBGRA<byte>, byte>(data, 256, 256, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -189,8 +189,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRGFloatTest() public void ConvertRGFloatTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgfloat"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgfloat");
RgbConverter.RGFloatToBGRA32(data, 256, 256, out byte[] originalDecodedData); OldRgbConverter.RGFloatToBGRA32(data, 256, 256, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRG<float>, float, ColorBGRA32, byte>(data, 256, 256, out byte[] newDecodedData); RgbConverter.Convert<ColorRG<float>, float, ColorBGRA<byte>, byte>(data, 256, 256, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -198,8 +198,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRGBAFloatTest() public void ConvertRGBAFloatTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgbafloat"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgbafloat");
RgbConverter.RGBAFloatToBGRA32(data, 256, 256, out byte[] originalDecodedData); OldRgbConverter.RGBAFloatToBGRA32(data, 256, 256, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRGBA<float>, float, ColorBGRA32, byte>(data, 256, 256, out byte[] newDecodedData); RgbConverter.Convert<ColorRGBA<float>, float, ColorBGRA<byte>, byte>(data, 256, 256, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -207,8 +207,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRGB9e5FloatTest() public void ConvertRGB9e5FloatTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgb9e5float"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgb9e5float");
RgbConverter.RGB9e5FloatToBGRA32(data, 256, 256, out byte[] originalDecodedData); OldRgbConverter.RGB9e5FloatToBGRA32(data, 256, 256, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRGB9e5, double, ColorBGRA32, byte>(data, 256, 256, out byte[] newDecodedData); RgbConverter.Convert<ColorRGB9e5, double, ColorBGRA<byte>, byte>(data, 256, 256, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -216,8 +216,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRG32Test() public void ConvertRG32Test()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rg32"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rg32");
RgbConverter.RG32ToBGRA32(data, 512, 512, out byte[] originalDecodedData); OldRgbConverter.RG32ToBGRA32(data, 512, 512, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRG<ushort>, ushort, ColorBGRA32, byte>(data, 512, 512, out byte[] newDecodedData); RgbConverter.Convert<ColorRG<ushort>, ushort, ColorBGRA<byte>, byte>(data, 512, 512, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -225,8 +225,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRGB48Test() public void ConvertRGB48Test()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgb48"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgb48");
RgbConverter.RGB48ToBGRA32(data, 512, 512, out byte[] originalDecodedData); OldRgbConverter.RGB48ToBGRA32(data, 512, 512, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRGB<ushort>, ushort, ColorBGRA32, byte>(data, 512, 512, out byte[] newDecodedData); RgbConverter.Convert<ColorRGB<ushort>, ushort, ColorBGRA<byte>, byte>(data, 512, 512, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
@@ -234,8 +234,8 @@ namespace AssetRipper.TextureDecoder.Tests
public void ConvertRGBA64Test() public void ConvertRGBA64Test()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgba64"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgba64");
RgbConverter.RGBA64ToBGRA32(data, 512, 512, out byte[] originalDecodedData); OldRgbConverter.RGBA64ToBGRA32(data, 512, 512, out byte[] originalDecodedData);
RgbConverter.Convert<ColorRGBA<ushort>, ushort, ColorBGRA32, byte>(data, 512, 512, out byte[] newDecodedData); RgbConverter.Convert<ColorRGBA<ushort>, ushort, ColorBGRA<byte>, byte>(data, 512, 512, out byte[] newDecodedData);
Assert.That(originalDecodedData, Is.EqualTo(newDecodedData)); Assert.That(originalDecodedData, Is.EqualTo(newDecodedData));
} }
} }
+28 -18
View File
@@ -1,4 +1,7 @@
namespace AssetRipper.TextureDecoder.Tests using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Tests
{ {
public sealed class RgbTests public sealed class RgbTests
{ {
@@ -9,7 +12,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = Rgb.RgbConverter.A8ToBGRA32(data.Slice(totalBytesRead), size, size, out _); int bytesRead = ConvertSquare<ColorA<byte>, byte>(data.Slice(totalBytesRead), size);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -22,7 +25,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = Rgb.RgbConverter.RGB24ToBGRA32(data.Slice(totalBytesRead), size, size, out _); int bytesRead = ConvertSquare<ColorRGB<byte>, byte>(data.Slice(totalBytesRead), size);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -35,7 +38,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = Rgb.RgbConverter.RGBA32ToBGRA32(data.Slice(totalBytesRead), size, size, out _); int bytesRead = ConvertSquare<ColorRGBA<byte>, byte>(data.Slice(totalBytesRead), size);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -48,7 +51,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = Rgb.RgbConverter.ARGB32ToBGRA32(data.Slice(totalBytesRead), size, size, out _); int bytesRead = ConvertSquare<ColorARGB<byte>, byte>(data.Slice(totalBytesRead), size);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -61,7 +64,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = Rgb.RgbConverter.RGB16ToBGRA32(data.Slice(totalBytesRead), size, size, out _); int bytesRead = ConvertSquare<ColorRGB16, byte>(data.Slice(totalBytesRead), size);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -74,7 +77,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = Rgb.RgbConverter.RG16ToBGRA32(data.Slice(totalBytesRead), size, size, out _); int bytesRead = ConvertSquare<ColorRG<byte>, byte>(data.Slice(totalBytesRead), size);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -87,7 +90,7 @@
int totalBytesRead = 0; int totalBytesRead = 0;
foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps foreach (int size in new int[] { 256, 128, 64, 32, 16, 8, 4, 2, 1 }) //mip maps
{ {
int bytesRead = Rgb.RgbConverter.R8ToBGRA32(data.Slice(totalBytesRead), size, size, out _); int bytesRead = ConvertSquare<ColorR<byte>, byte>(data.Slice(totalBytesRead), size);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
Assert.That(totalBytesRead, Is.EqualTo(data.Length)); Assert.That(totalBytesRead, Is.EqualTo(data.Length));
@@ -97,7 +100,7 @@
public void ConvertRHalfTest() public void ConvertRHalfTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rhalf"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rhalf");
int bytesRead = Rgb.RgbConverter.RHalfToBGRA32(data, 256, 256, out _); int bytesRead = ConvertSquare<ColorR<Half>, Half>(data, 256);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -105,7 +108,7 @@
public void ConvertRGHalfTest() public void ConvertRGHalfTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rghalf"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rghalf");
int bytesRead = Rgb.RgbConverter.RGHalfToBGRA32(data, 256, 256, out _); int bytesRead = ConvertSquare<ColorRG<Half>, Half>(data, 256);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -113,7 +116,7 @@
public void ConvertRGBAHalfTest() public void ConvertRGBAHalfTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgbahalf"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgbahalf");
int bytesRead = Rgb.RgbConverter.RGBAHalfToBGRA32(data, 256, 256, out _); int bytesRead = ConvertSquare<ColorRGBA<Half>, Half>(data, 256);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -121,7 +124,7 @@
public void ConvertRFloatTest() public void ConvertRFloatTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rfloat"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rfloat");
int bytesRead = Rgb.RgbConverter.RFloatToBGRA32(data, 256, 256, out _); int bytesRead = ConvertSquare<ColorR<float>, float>(data, 256);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -129,7 +132,7 @@
public void ConvertRGFloatTest() public void ConvertRGFloatTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgfloat"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgfloat");
int bytesRead = Rgb.RgbConverter.RGFloatToBGRA32(data, 256, 256, out _); int bytesRead = ConvertSquare<ColorRG<float>, float>(data, 256);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -137,7 +140,7 @@
public void ConvertRGBAFloatTest() public void ConvertRGBAFloatTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgbafloat"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgbafloat");
int bytesRead = Rgb.RgbConverter.RGBAFloatToBGRA32(data, 256, 256, out _); int bytesRead = ConvertSquare<ColorRGBA<float>, float>(data, 256);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -145,7 +148,7 @@
public void ConvertRGB9e5FloatTest() public void ConvertRGB9e5FloatTest()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgb9e5float"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgb9e5float");
int bytesRead = Rgb.RgbConverter.RGB9e5FloatToBGRA32(data, 256, 256, out _); int bytesRead = ConvertSquare<ColorRGB9e5, double>(data, 256);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -153,7 +156,7 @@
public void ConvertRG32Test() public void ConvertRG32Test()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rg32"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rg32");
int bytesRead = Rgb.RgbConverter.RG32ToBGRA32(data, 512, 512, out _); int bytesRead = ConvertSquare<ColorRG<ushort>, ushort>(data, 512);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -161,7 +164,7 @@
public void ConvertRGB48Test() public void ConvertRGB48Test()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgb48"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgb48");
int bytesRead = Rgb.RgbConverter.RGB48ToBGRA32(data, 512, 512, out _); int bytesRead = ConvertSquare<ColorRGB<ushort>, ushort>(data, 512);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
@@ -169,8 +172,15 @@
public void ConvertRGBA64Test() public void ConvertRGBA64Test()
{ {
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgba64"); ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.RgbTestFiles + "test.rgba64");
int bytesRead = Rgb.RgbConverter.RGBA64ToBGRA32(data, 512, 512, out _); int bytesRead = ConvertSquare<ColorRGBA<ushort>, ushort>(data, 512);
Assert.That(bytesRead, Is.EqualTo(data.Length)); Assert.That(bytesRead, Is.EqualTo(data.Length));
} }
private static int ConvertSquare<TColor, TChannel>(ReadOnlySpan<byte> data, int size)
where TColor : unmanaged, IColor<TChannel>
where TChannel : unmanaged
{
return RgbConverter.Convert<TColor, TChannel, ColorBGRA<byte>, byte>(data, size, size, out _);
}
} }
} }
@@ -13,12 +13,12 @@ namespace AssetRipper.TextureDecoder.Tests
public void DecompressYUY2Test() public void DecompressYUY2Test()
{ {
byte[] input = File.ReadAllBytes(TestFileFolders.Yuy2TestFiles + "test.yuy2"); byte[] input = File.ReadAllBytes(TestFileFolders.Yuy2TestFiles + "test.yuy2");
int bytesRead = Yuy2Decoder.DecompressYUY2<ColorBGRA32, byte>(input, TestImageWidth, TestImageHeight, out byte[] decodedData); int bytesRead = Yuy2Decoder.DecompressYUY2<ColorBGRA<byte>, byte>(input, TestImageWidth, TestImageHeight, out byte[] decodedData);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.That(bytesRead, Is.EqualTo(input.Length)); Assert.That(bytesRead, Is.EqualTo(input.Length));
Assert.That(decodedData, Has.Length.EqualTo(TestImageWidth * TestImageHeight * Unsafe.SizeOf<ColorBGRA32>())); Assert.That(decodedData, Has.Length.EqualTo(TestImageWidth * TestImageHeight * Unsafe.SizeOf<ColorBGRA<byte>>()));
}); }
} }
} }
} }
+24
View File
@@ -0,0 +1,24 @@
<Solution>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
<File Path=".gitignore" />
<File Path="Directory.Build.props" />
<File Path="LICENSE" />
<File Path="README.md" />
</Folder>
<Folder Name="/Solution Items/Licenses/">
<File Path="Licenses/bcdec.md" />
<File Path="Licenses/uTinyRipper.md" />
</Folder>
<Folder Name="/Solution Items/Workflows/">
<File Path=".github/workflows/build.yml" />
</Folder>
<Project Path="AssetRipper.TextureDecoder.ColorGenerator/AssetRipper.TextureDecoder.ColorGenerator.csproj" />
<Project Path="AssetRipper.TextureDecoder.ConsoleApp/AssetRipper.TextureDecoder.ConsoleApp.csproj" />
<Project Path="AssetRipper.TextureDecoder.Paths/AssetRipper.TextureDecoder.Paths.csproj" />
<Project Path="AssetRipper.TextureDecoder.SourceGeneration.Common/AssetRipper.TextureDecoder.SourceGeneration.Common.csproj" />
<Project Path="AssetRipper.TextureDecoder.TestGenerator/AssetRipper.TextureDecoder.TestGenerator.csproj" />
<Project Path="AssetRipper.TextureDecoder.Tests/AssetRipper.TextureDecoder.Tests.csproj" />
<Project Path="AssetRipper.TextureDecoder/AssetRipper.TextureDecoder.csproj" />
<Project Path="IntegerConversion.ConstantEstimation/IntegerConversion.ConstantEstimation.csproj" />
</Solution>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<IsTrimmable>true</IsTrimmable> <TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<PackageId>AssetRipper.TextureDecoder</PackageId> <PackageId>AssetRipper.TextureDecoder</PackageId>
@@ -10,17 +10,16 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/AssetRipper/TextureDecoder</PackageProjectUrl> <PackageProjectUrl>https://github.com/AssetRipper/TextureDecoder</PackageProjectUrl>
<Copyright>Copyright (c) 2022 - 2024 ds5678</Copyright> <Copyright>Copyright (c) 2022 - 2026 ds5678</Copyright>
<Description>Managed library for decoding textures in multiple formats.</Description> <Description>Managed library for decoding textures in multiple formats.</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>bin\TextureDecoder.xml</DocumentationFile>
<NoWarn>1591</NoWarn> <NoWarn>1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0"> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.400">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
+49 -42
View File
@@ -1,3 +1,5 @@
using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Formats;
using System.Buffers.Binary; using System.Buffers.Binary;
namespace AssetRipper.TextureDecoder.Astc namespace AssetRipper.TextureDecoder.Astc
@@ -12,55 +14,59 @@ 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]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
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(blockWidth);
ValidateBlockDimension(blockHeight); ValidateBlockDimension(blockHeight);
int bcw = (width + blockWidth - 1) / blockWidth; int bcw = (width + blockWidth - 1) / blockWidth;
int bch = (height + blockHeight - 1) / blockHeight; int bch = (height + blockHeight - 1) / blockHeight;
int clen_last = (width + blockWidth - 1) % blockWidth + 1; 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; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 16) 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; int clen = s < bcw - 1 ? blockWidth : clen_last;
for (int i = 0, y = t * blockHeight; i < blockHeight && y < height; i++, y++) for (int i = 0, y = t * blockHeight; i < blockHeight && y < height; i++, y++)
{ {
int outputOffsetUInt32 = t * blockHeight * width + s * blockWidth + i * width; int outputOffsetUInt32 = t * blockHeight * width + s * blockWidth + i * width;
for (int j = 0; j < clen; j++) 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; 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) 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[..(blockWidth * blockHeight)].Fill(c);
{
output[i] = c;
}
} }
else else
{ {
@@ -674,7 +680,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) if (block.dual_plane != 0)
{ {
@@ -689,7 +695,7 @@ namespace AssetRipper.TextureDecoder.Astc
byte g = SelectColor(block.endpoints[p][1], block.endpoints[p][5], block.weights[i * 2 + ps[1]]); 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 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]]); 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 else
@@ -700,7 +706,7 @@ namespace AssetRipper.TextureDecoder.Astc
byte g = SelectColor(block.endpoints[0][1], block.endpoints[0][5], block.weights[i * 2 + ps[1]]); 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 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]]); 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 +719,7 @@ namespace AssetRipper.TextureDecoder.Astc
byte g = SelectColor(block.endpoints[p][1], block.endpoints[p][5], block.weights[i * 2]); 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 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]); 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 else
@@ -724,7 +730,7 @@ namespace AssetRipper.TextureDecoder.Astc
byte g = SelectColor(block.endpoints[0][1], block.endpoints[0][5], block.weights[i * 2]); 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 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]); 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 +855,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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static byte BitReverseU8(byte c, int bits) private static byte BitReverseU8(byte c, int bits)
{ {
@@ -883,6 +883,11 @@ namespace AssetRipper.TextureDecoder.Astc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static int GetBits(ReadOnlySpan<byte> input, int bit, int len) private static int GetBits(ReadOnlySpan<byte> input, int bit, int len)
{ {
if (bit < 0 || input.Length * 8 < bit + len)
{
// Invalid input, but we should return something instead of throwing an exception
return default;
}
ReadOnlySpan<byte> slice = input[(bit / 8)..]; ReadOnlySpan<byte> slice = input[(bit / 8)..];
int bitBuffer; int bitBuffer;
if (slice.Length >= sizeof(int)) if (slice.Length >= sizeof(int))
@@ -996,10 +1001,12 @@ namespace AssetRipper.TextureDecoder.Astc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static byte SelectColor(int v0, int v1, int weight) private static byte SelectColor(int v0, int v1, int weight)
{ {
return (byte)(((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * 255 + 32768) / 65536); const int SignAdjustment = short.MaxValue + 1;
const int MaxValue = ushort.MaxValue + 1;
return (byte)(((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * byte.MaxValue + SignAdjustment) / MaxValue);
} }
private static readonly byte[] BitReverseTable = private static ReadOnlySpan<byte> BitReverseTable =>
[ [
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
@@ -1019,15 +1026,15 @@ namespace AssetRipper.TextureDecoder.Astc
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
]; ];
private static readonly int[] WeightPrecTableA = [0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0]; private static ReadOnlySpan<int> WeightPrecTableA => [0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0];
private static readonly int[] WeightPrecTableB = [0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5]; private static ReadOnlySpan<int> WeightPrecTableB => [0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5];
private static readonly int[] CemTableA = [0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0]; private static ReadOnlySpan<int> CemTableA => [0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0];
private static readonly int[] CemTableB = [8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1]; private static ReadOnlySpan<int> CemTableB => [8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1];
private static readonly int[] DImt = [0, 2, 4, 5, 7]; private static ReadOnlySpan<int> DImt => [0, 2, 4, 5, 7];
private static readonly int[] DImq = [0, 3, 5]; private static ReadOnlySpan<int> DImq => [0, 3, 5];
private static readonly int[] DITritsTable = private static ReadOnlySpan<int> DITritsTable =>
[ [
0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2,
0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1,
@@ -1035,14 +1042,14 @@ namespace AssetRipper.TextureDecoder.Astc
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
]; ];
private static readonly int[] DIQuintsTable = private static ReadOnlySpan<int> DIQuintsTable =>
[ [
0, 1, 2, 3, 4, 0, 4, 4, 0, 1, 2, 3, 4, 1, 4, 4, 0, 1, 2, 3, 4, 2, 4, 4, 0, 1, 2, 3, 4, 3, 4, 4, 0, 1, 2, 3, 4, 0, 4, 0, 0, 1, 2, 3, 4, 1, 4, 1, 0, 1, 2, 3, 4, 2, 4, 2, 0, 1, 2, 3, 4, 3, 4, 3, 0, 1, 2, 3, 4, 0, 2, 3, 0, 1, 2, 3, 4, 1, 2, 3, 0, 1, 2, 3, 4, 2, 2, 3, 0, 1, 2, 3, 4, 3, 2, 3, 0, 1, 2, 3, 4, 0, 0, 1, 0, 1, 2, 3, 4, 1, 0, 1, 0, 1, 2, 3, 4, 2, 0, 1, 0, 1, 2, 3, 4, 3, 0, 1, 0, 1, 2, 3, 4, 0, 4, 4, 0, 1, 2, 3, 4, 1, 4, 4, 0, 1, 2, 3, 4, 2, 4, 4, 0, 1, 2, 3, 4, 3, 4, 4, 0, 1, 2, 3, 4, 0, 4, 0, 0, 1, 2, 3, 4, 1, 4, 1, 0, 1, 2, 3, 4, 2, 4, 2, 0, 1, 2, 3, 4, 3, 4, 3, 0, 1, 2, 3, 4, 0, 2, 3, 0, 1, 2, 3, 4, 1, 2, 3, 0, 1, 2, 3, 4, 2, 2, 3, 0, 1, 2, 3, 4, 3, 2, 3, 0, 1, 2, 3, 4, 0, 0, 1, 0, 1, 2, 3, 4, 1, 0, 1, 0, 1, 2, 3, 4, 2, 0, 1, 0, 1, 2, 3, 4, 3, 0, 1,
0, 0, 0, 0, 0, 4, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 0, 0, 0, 0, 0, 4, 0, 4, 1, 1, 1, 1, 1, 4, 1, 4, 2, 2, 2, 2, 2, 4, 2, 4, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 0, 0, 0, 0, 0, 4, 0, 4, 1, 1, 1, 1, 1, 4, 1, 4, 2, 2, 2, 2, 2, 4, 2, 4, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3,
0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 3, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 3, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4,
]; ];
private static readonly int[] DETritsTable = [0, 204, 93, 44, 22, 11, 5]; private static ReadOnlySpan<int> DETritsTable => [0, 204, 93, 44, 22, 11, 5];
private static readonly int[] DEQuintsTable = [0, 113, 54, 26, 13, 6]; private static ReadOnlySpan<int> DEQuintsTable => [0, 113, 54, 26, 13, 6];
} }
} }
+55 -29
View File
@@ -1,34 +1,48 @@
using AssetRipper.TextureDecoder.Rgb;
using System.Buffers.Binary; using System.Buffers.Binary;
namespace AssetRipper.TextureDecoder.Atc namespace AssetRipper.TextureDecoder.Atc
{ {
public static class AtcDecoder 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]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return DecompressAtcRgb4(input, width, height, output); 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 bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16]; Span<TOutputColor> buf = stackalloc TOutputColor[16];
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 8) 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; int clen = s < bcw - 1 ? 4 : clen_last;
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) 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++) 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; 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]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return DecompressAtcRgba8(input, width, height, output); 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 bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16]; Span<TOutputColor> buf = stackalloc TOutputColor[16];
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 16) 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; int clen = s < bcw - 1 ? 4 : clen_last;
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) 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++) 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; 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]; Span<int> colors = stackalloc int[16];
int c0 = input.ReadAtOffset<ushort>(0); int c0 = input.ReadAtOffset<ushort>(0);
@@ -83,12 +112,14 @@ namespace AssetRipper.TextureDecoder.Atc
int cb = colors[cidx * 4 + 0]; int cb = colors[cidx * 4 + 0];
int cg = colors[cidx * 4 + 1]; int cg = colors[cidx * 4 + 1];
int cr = colors[cidx * 4 + 2]; 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; 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]; Span<int> alphas = stackalloc int[16];
ulong avalue = BinaryPrimitives.ReadUInt64LittleEndian(input); ulong avalue = BinaryPrimitives.ReadUInt64LittleEndian(input);
@@ -112,7 +143,7 @@ namespace AssetRipper.TextureDecoder.Atc
int cr = colors[cidx * 4 + 2]; int cr = colors[cidx * 4 + 2];
int aidx = unchecked((int)aindex & 7); int aidx = unchecked((int)aindex & 7);
int ca = alphas[aidx]; 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; cindex >>= 2;
aindex >>= 3; aindex >>= 3;
} }
@@ -121,6 +152,7 @@ namespace AssetRipper.TextureDecoder.Atc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static T ReadAtOffset<T>(this ReadOnlySpan<byte> input, int offset) where T : unmanaged private static T ReadAtOffset<T>(this ReadOnlySpan<byte> input, int offset) where T : unmanaged
{ {
ThrowHelper.ThrowIfNotLittleEndian();
return MemoryMarshal.Read<T>(input.Slice(offset)); return MemoryMarshal.Read<T>(input.Slice(offset));
} }
@@ -159,9 +191,9 @@ namespace AssetRipper.TextureDecoder.Atc
colors[13] = Extend((c1 >> 5) & 0x3F, 6, 8); colors[13] = Extend((c1 >> 5) & 0x3F, 6, 8);
colors[14] = Extend((c1 >> 11) & 0x1F, 5, 8); colors[14] = Extend((c1 >> 11) & 0x1F, 5, 8);
colors[4] = Math.Max(0, colors[8] - colors[12] / 4); colors[4] = int.Max(0, colors[8] - colors[12] / 4);
colors[5] = Math.Max(0, colors[9] - colors[13] / 4); colors[5] = int.Max(0, colors[9] - colors[13] / 4);
colors[6] = Math.Max(0, colors[10] - colors[14] / 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) // bit-pattern replicating scaling (can at most double the bits)
return (value << (to - from)) | (value >> (from * 2 - to)); 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. /// The size of an encoded block, in bytes.
/// </summary> /// </summary>
public const int BlockSize = 8; 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>()]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress(input, width, height, output); 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; 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), buffer);
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), output.Slice(outputOffset), width * 4); BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize; inputOffset += BlockSize;
} }
} }
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
return inputOffset; return inputOffset;
} }
+39 -9
View File
@@ -9,26 +9,56 @@ public static class Bc2
/// The size of an encoded block, in bytes. /// The size of an encoded block, in bytes.
/// </summary> /// </summary>
public const int BlockSize = 16; 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>()]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress(input, width, height, output); 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; 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, BlockSize), buffer);
BcHelpers.DecompressBc2(input.Slice(inputOffset), output.Slice(outputOffset), width * 4); BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize; inputOffset += BlockSize;
} }
} }
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
return inputOffset; return inputOffset;
} }
+39 -9
View File
@@ -9,26 +9,56 @@ public static class Bc3
/// The size of an encoded block, in bytes. /// The size of an encoded block, in bytes.
/// </summary> /// </summary>
public const int BlockSize = 16; 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>()]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress(input, width, height, output); 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; 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, BlockSize), buffer);
BcHelpers.DecompressBc3(input.Slice(inputOffset), output.Slice(outputOffset), width * 4); BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize; inputOffset += BlockSize;
} }
} }
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
return inputOffset; return inputOffset;
} }
+39 -13
View File
@@ -10,30 +10,56 @@ public static class Bc4
/// The size of an encoded block, in bytes. /// The size of an encoded block, in bytes.
/// </summary> /// </summary>
public const int BlockSize = 8; 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>()]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress(input, width, height, output); 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>>(); ThrowHelper.ThrowIfNotLittleEndian();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize); return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize); }
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; 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, BlockSize), buffer);
BcHelpers.DecompressBc4(input.Slice(inputOffset), buffer.Slice(bufferOffset), width); BcHelpers.CopyBufferToOutput<ColorR<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorR<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize; inputOffset += BlockSize;
} }
} }
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
return inputOffset; return inputOffset;
} }
+39 -13
View File
@@ -10,30 +10,56 @@ public static class Bc5
/// The size of an encoded block, in bytes. /// The size of an encoded block, in bytes.
/// </summary> /// </summary>
public const int BlockSize = 16; 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>()]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress(input, width, height, output); 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>>(); ThrowHelper.ThrowIfNotLittleEndian();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize); return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize); }
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; 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, BlockSize), buffer);
BcHelpers.DecompressBc5(input.Slice(inputOffset), buffer.Slice(bufferOffset), width * 2); BcHelpers.CopyBufferToOutput<ColorRG<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRG<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
inputOffset += BlockSize; inputOffset += BlockSize;
} }
} }
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
return inputOffset; return inputOffset;
} }
+39 -17
View File
@@ -10,34 +10,56 @@ public static class Bc6h
/// The size of an encoded block, in bytes. /// The size of an encoded block, in bytes.
/// </summary> /// </summary>
public const int BlockSize = 16; 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>()]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress(input, width, height, isSigned, output); 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>>(); ThrowHelper.ThrowIfNotLittleEndian();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize); return Decompress<TOutputColor, TOutputChannelValue>(input, width, height, isSigned, MemoryMarshal.Cast<byte, TOutputColor>(output));
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize); }
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; 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, isSigned);
BcHelpers.DecompressBc6h( BcHelpers.CopyBufferToOutput<ColorRGB<Half>, Half, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGB<Half>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
input.Slice(inputOffset, BlockSize),
buffer.Slice(outputOffset),
width * 3,
isSigned);
inputOffset += BlockSize; inputOffset += BlockSize;
} }
} }
RgbConverter.Convert<ColorRGB<Half>, Half, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
return inputOffset; return inputOffset;
} }
+2 -2
View File
@@ -211,7 +211,7 @@ internal static class Bc6hTables
] ]
]; ];
internal static int[] AWeight3 { get; } = [0, 9, 18, 27, 37, 46, 55, 64]; internal static ReadOnlySpan<int> AWeight3 => [0, 9, 18, 27, 37, 46, 55, 64];
internal static int[] AWeight4 { get; } = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64]; internal static ReadOnlySpan<int> AWeight4 => [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
} }
+25 -10
View File
@@ -21,29 +21,44 @@ public static class Bc7
/// The size of the natural pixel type. /// The size of the natural pixel type.
/// </summary> /// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>(); 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>()]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return Decompress(input, width, height, output); 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; int inputOffset = 0;
for (int i = 0; i < height; i += BlockHeight) for (int i = 0; i < height; i += BlockHeight)
{ {
for (int j = 0; j < width; j += BlockWidth) for (int j = 0; j < width; j += BlockWidth)
{ {
int outputOffset = ((i * width) + j) * PixelSize; BcHelpers.DecompressBc7(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.DecompressBc7( BcHelpers.CopyBufferToOutput<ColorRGBA<byte>, byte, TOutputColor, TOutputChannelValue>(MemoryMarshal.Cast<byte, ColorRGBA<byte>>(buffer), output, width, height, j, i, BlockWidth, BlockHeight);
input.Slice(inputOffset, BlockSize),
output.Slice(outputOffset),
width * 4);
inputOffset += BlockSize; inputOffset += BlockSize;
} }
} }
RgbConverter.Convert<ColorRGBA<byte>, byte, ColorBGRA32, byte>(output, width, height, output);
return inputOffset; return inputOffset;
} }
+3 -3
View File
@@ -789,11 +789,11 @@ internal static class Bc7Tables
] ]
]; ];
internal static int[] AWeight2 { get; } = [0, 21, 43, 64]; internal static ReadOnlySpan<int> AWeight2 => [0, 21, 43, 64];
internal static int[] AWeight3 { get; } = [0, 9, 18, 27, 37, 46, 55, 64]; internal static ReadOnlySpan<int> AWeight3 => [0, 9, 18, 27, 37, 46, 55, 64];
internal static int[] AWeight4 { get; } = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64]; internal static ReadOnlySpan<int> AWeight4 => [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
internal const byte bcdec_bc7_sModeHasPBits = 0b11001011; internal const byte bcdec_bc7_sModeHasPBits = 0b11001011;
} }
+152 -95
View File
@@ -1,47 +1,57 @@
using System.Buffers.Binary; using AssetRipper.TextureDecoder.Rgb;
using System.Buffers.Binary;
using System.Diagnostics;
namespace AssetRipper.TextureDecoder.Bc; namespace AssetRipper.TextureDecoder.Bc;
internal static class BcHelpers 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); 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); ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
SharpAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch); 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); ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
SmoothAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch, 4); 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); 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, decompressedBlock, destinationPitch, 2);
SmoothAlphaBlock(compressedBlock.Slice(8), decompressedBlock.Slice(1), destinationPitch, 2); SmoothAlphaBlock(compressedBlock.Slice(8), decompressedBlock.Slice(1), destinationPitch, 2);
} }
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch, bool isSigned) public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, bool isSigned)
{ {
BitStream bstream = new() Debug.Assert(decompressedBlock.Length == Bc6h.DecodedBlockSize);
{
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock), BitStream bstream = new(compressedBlock);
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong))) Span<uint> r = stackalloc uint[4]; // wxyz
}; Span<uint> g = stackalloc uint[4];
Span<int> r = stackalloc int[4]; // wxyz Span<uint> b = stackalloc uint[4];
Span<int> g = stackalloc int[4];
Span<int> b = stackalloc int[4];
int decompressedOffset = 0; int decompressedOffset = 0;
@@ -52,7 +62,7 @@ internal static class BcHelpers
int mode; int mode;
//modes >= 11 (10 in my code) are using 0 one, others will read it from the bitstream //modes >= 11 (10 in my code) are using 0 one, others will read it from the bitstream
int partition; uint partition;
switch (ReadBc6hModeBits(ref bstream)) switch (ReadBc6hModeBits(ref bstream))
{ {
@@ -452,15 +462,7 @@ internal static class BcHelpers
// Do not use these in your encoder. If the hardware is passed blocks // Do not use these in your encoder. If the hardware is passed blocks
// with one of these modes specified, the resulting decompressed block // with one of these modes specified, the resulting decompressed block
// must contain all zeroes in all channels except for the alpha channel. // must contain all zeroes in all channels except for the alpha channel.
for (int i = 0; i < 4; ++i) decompressedBlock.Clear();
{
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);
}
return; return;
} }
@@ -477,7 +479,7 @@ internal static class BcHelpers
// Mode 11 (like Mode 10) does not use delta compression, // Mode 11 (like Mode 10) does not use delta compression,
// and instead stores both color endpoints explicitly. // and instead stores both color endpoints explicitly.
if ((mode != 9 && mode != 10) || isSigned) if ((mode is not 9 and not 10) || isSigned)
{ {
for (int i = 1; i < (numPartitions + 1) * 2; ++i) for (int i = 1; i < (numPartitions + 1) * 2; ++i)
{ {
@@ -487,7 +489,7 @@ internal static class BcHelpers
} }
} }
if (mode != 9 && mode != 10) if (mode is not 9 and not 10)
{ {
for (int i = 1; i < (numPartitions + 1) * 2; ++i) for (int i = 1; i < (numPartitions + 1) * 2; ++i)
{ {
@@ -520,7 +522,7 @@ internal static class BcHelpers
} }
partitionSet &= 0x01; partitionSet &= 0x01;
int index = bstream.ReadBits(indexBits); int index = bstream.ReadBits(indexBits).ToSigned();
int ep_i = (partitionSet * 2); int ep_i = (partitionSet * 2);
@@ -539,15 +541,15 @@ internal static class BcHelpers
BinaryPrimitives.WriteUInt16LittleEndian(decompressedBlock.Slice(bOffset), bFinal); BinaryPrimitives.WriteUInt16LittleEndian(decompressedBlock.Slice(bOffset), bFinal);
} }
decompressedOffset += destinationPitch * sizeof(ushort); decompressedOffset += 4 * 3 * sizeof(ushort);
} }
static int ReadBc6hModeBits(ref BitStream bstream) static uint ReadBc6hModeBits(ref BitStream bstream)
{ {
int twoBits = bstream.ReadBits(2); uint twoBits = bstream.ReadBits(2);
if (twoBits > 1) if (twoBits > 1)
{ {
int threeBits = bstream.ReadBits(3); uint threeBits = bstream.ReadBits(3);
return (threeBits << 2) | twoBits; return (threeBits << 2) | twoBits;
} }
else else
@@ -557,61 +559,54 @@ internal static class BcHelpers
} }
} }
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch) public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{ {
BitStream bstream = new() Debug.Assert(decompressedBlock.Length == Bc7.DecodedBlockSize);
{
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock),
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong)))
};
Span2D<int> endpoints = new(stackalloc int[6 * 4], 6, 4);
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
int decompressedOffset = 0; BitStream bstream = new(compressedBlock);
Span2D<uint> endpoints = new(stackalloc uint[6 * 4], 6, 4);
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
int mode = ReadBc7Mode(ref bstream); int mode = ReadBc7Mode(ref bstream);
// unexpected mode, clear the block (transparent black) // unexpected mode, clear the block (transparent black)
if (mode >= 8) if (mode >= 8)
{ {
for (int i = 0; i < 4; ++i) decompressedBlock.Clear();
{
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;
}
return; return;
} }
int partition = 0; uint partition = 0;
int numPartitions = 1; int numPartitions = 1;
int rotation = 0; uint rotation = 0;
int indexSelectionBit = 0; uint indexSelectionBit = 0;
if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 7) switch (mode)
{ {
numPartitions = (mode == 0 || mode == 2) ? 3 : 2; case 0:
partition = bstream.ReadBits((mode == 0) ? 4 : 6); numPartitions = 3;
partition = bstream.ReadBits(4);
break;
case 1 or 3 or 7:
numPartitions = 2;
partition = bstream.ReadBits(6);
break;
case 2:
numPartitions = 3;
partition = bstream.ReadBits(6);
break;
case 4:
rotation = bstream.ReadBits(2);
indexSelectionBit = bstream.ReadBit();
break;
case 5:
rotation = bstream.ReadBits(2);
break;
} }
int numEndpoints = numPartitions * 2; int numEndpoints = numPartitions * 2;
if (mode == 4 || mode == 5)
{
rotation = bstream.ReadBits(2);
if (mode == 4)
{
indexSelectionBit = bstream.ReadBit();
}
}
// Extract endpoints // Extract endpoints
// RGB // RGB
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
@@ -646,8 +641,8 @@ internal static class BcHelpers
// if P-bit is shared // if P-bit is shared
if (mode == 1) if (mode == 1)
{ {
int i = bstream.ReadBit(); uint i = bstream.ReadBit();
int j = bstream.ReadBit(); uint j = bstream.ReadBit();
// rgb component-wise insert pbits // rgb component-wise insert pbits
for (int k = 0; k < 3; ++k) for (int k = 0; k < 3; ++k)
@@ -663,7 +658,7 @@ internal static class BcHelpers
// unique P-bit per endpoint // unique P-bit per endpoint
for (int i = 0; i < numEndpoints; ++i) for (int i = 0; i < numEndpoints; ++i)
{ {
int j = bstream.ReadBit(); uint j = bstream.ReadBit();
for (int k = 0; k < 4; ++k) for (int k = 0; k < 4; ++k)
{ {
endpoints[i, k] |= j; endpoints[i, k] |= j;
@@ -705,10 +700,25 @@ internal static class BcHelpers
} }
// Determine weights tables // Determine weights tables
int indexBits = (mode == 0 || mode == 1) ? 3 : ((mode == 6) ? 4 : 2); int indexBits = mode switch
int indexBits2 = (mode == 4) ? 3 : ((mode == 5) ? 2 : 0); {
int[] weights = (indexBits == 2) ? Bc7Tables.AWeight2 : ((indexBits == 3) ? Bc7Tables.AWeight3 : Bc7Tables.AWeight4); 0 or 1 => 3,
int[] weights2 = (indexBits2 == 2) ? Bc7Tables.AWeight2 : Bc7Tables.AWeight3; 6 => 4,
_ => 2
};
int indexBits2 = mode switch
{
4 => 3,
5 => 2,
_ => 0
};
ReadOnlySpan<int> weights = indexBits switch
{
2 => Bc7Tables.AWeight2,
3 => Bc7Tables.AWeight3,
_ => Bc7Tables.AWeight4
};
ReadOnlySpan<int> weights2 = (indexBits2 == 2) ? Bc7Tables.AWeight2 : Bc7Tables.AWeight3;
// Quite inconvenient that indices aren't interleaved so we have to make 2 passes here // Quite inconvenient that indices aren't interleaved so we have to make 2 passes here
// Pass #1: collecting color indices // Pass #1: collecting color indices
@@ -726,7 +736,7 @@ internal static class BcHelpers
indexBits--; indexBits--;
} }
indices[i, j] = bstream.ReadBits(indexBits); indices[i, j] = bstream.ReadBits(indexBits).ToSigned();
} }
} }
@@ -742,10 +752,10 @@ internal static class BcHelpers
int index = indices[i, j]; int index = indices[i, j];
int r; uint r;
int g; uint g;
int b; uint b;
int a; uint a;
if (indexBits2 == 0) if (indexBits2 == 0)
{ {
@@ -756,7 +766,7 @@ internal static class BcHelpers
} }
else else
{ {
int index2 = bstream.ReadBits((i | j) != 0 ? indexBits2 : (indexBits2 - 1)); int index2 = bstream.ReadBits((i | j) != 0 ? indexBits2 : (indexBits2 - 1)).ToSigned();
// The index value for interpolating color comes from the secondary index bits for the texel // The index value for interpolating color comes from the secondary index bits for the texel
// if the mode has an index selection bit and its value is one, and from the primary index bits otherwise. // if the mode has an index selection bit and its value is one, and from the primary index bits otherwise.
// The alpha index comes from the secondary index bits if the block has a secondary index // The alpha index comes from the secondary index bits if the block has a secondary index
@@ -800,23 +810,23 @@ internal static class BcHelpers
break; break;
} }
decompressedBlock[decompressedOffset + (j * 4) + 0] = (byte)r; int offset = (i * 16) + (j * 4);
decompressedBlock[decompressedOffset + (j * 4) + 1] = (byte)g; decompressedBlock[offset + 0] = (byte)r;
decompressedBlock[decompressedOffset + (j * 4) + 2] = (byte)b; decompressedBlock[offset + 1] = (byte)g;
decompressedBlock[decompressedOffset + (j * 4) + 3] = (byte)a; decompressedBlock[offset + 2] = (byte)b;
decompressedBlock[offset + 3] = (byte)a;
} }
decompressedOffset += destinationPitch;
} }
static int ReadBc7Mode(ref BitStream bstream) static int ReadBc7Mode(ref BitStream bstream)
{ {
int i = 0; // Read bits until we find a non-zero bit, up to 8 bits.
while (i < 8 && bstream.ReadBit() == 0) // The mode is the number of bits read.
{ uint eightBits = bstream.PeakBits(8);
i++; int zeros = BitOperations.TrailingZeroCount(eightBits);
} int bitCount = zeros >= 8 ? 8 : zeros + 1;
return i; bstream.Advance(bitCount);
return int.Min(zeros, 8);
} }
} }
@@ -949,6 +959,11 @@ internal static class BcHelpers
return (val << (32 - bits)) >> (32 - bits); return (val << (32 - bits)) >> (32 - bits);
} }
private static uint ExtendSign(uint val, int bits)
{
return ExtendSign(val.ToSigned(), bits).ToUnsigned();
}
public static int TransformInverse(int val, int a0, int bits, bool isSigned) public static int TransformInverse(int val, int a0, int bits, bool isSigned)
{ {
// If the precision of A0 is "p" bits, then the transform algorithm is: // If the precision of A0 is "p" bits, then the transform algorithm is:
@@ -961,6 +976,11 @@ internal static class BcHelpers
return val; return val;
} }
private static uint TransformInverse(uint val, uint a0, int bits, bool isSigned)
{
return TransformInverse(val.ToSigned(), a0.ToSigned(), bits, isSigned).ToUnsigned();
}
/// <summary> /// <summary>
/// Essentially copy-paste from documentation /// Essentially copy-paste from documentation
/// </summary> /// </summary>
@@ -1028,11 +1048,21 @@ internal static class BcHelpers
return unq; return unq;
} }
private static uint Unquantize(uint val, int bits, bool isSigned)
{
return Unquantize(val.ToSigned(), bits, isSigned).ToUnsigned();
}
public static int Interpolate(int a, int b, ReadOnlySpan<int> weights, int index) public static int Interpolate(int a, int b, ReadOnlySpan<int> weights, int index)
{ {
return ((a * (64 - weights[index])) + (b * weights[index]) + 32) >> 6; return ((a * (64 - weights[index])) + (b * weights[index]) + 32) >> 6;
} }
private static uint Interpolate(uint a, uint b, ReadOnlySpan<int> weights, int index)
{
return Interpolate(a.ToSigned(), b.ToSigned(), weights, index).ToUnsigned();
}
public static ushort FinishUnquantize(int val, bool isSigned) public static ushort FinishUnquantize(int val, bool isSigned)
{ {
if (!isSigned) if (!isSigned)
@@ -1041,7 +1071,7 @@ internal static class BcHelpers
} }
else else
{ {
val = (val < 0) ? -(((-val) * 31) >> 5) : (val * 31) >> 5; // scale the magnitude by 31 / 32 val = (val < 0) ? (((-val) * 31) >> 5) : (val * 31) >> 5; // scale the magnitude by 31 / 32
int s = 0; int s = 0;
if (val < 0) if (val < 0)
{ {
@@ -1052,8 +1082,35 @@ internal static class BcHelpers
} }
} }
public static void SwapValues(ref int a, ref int b) private static ushort FinishUnquantize(uint val, bool isSigned)
{
return FinishUnquantize(val.ToSigned(), isSigned);
}
public static void SwapValues(ref uint a, ref uint b)
{ {
(a, b) = (b, a); (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>();
}
}
}
} }
+53 -27
View File
@@ -1,27 +1,59 @@
namespace AssetRipper.TextureDecoder.Bc; using System.Buffers.Binary;
internal struct BitStream namespace AssetRipper.TextureDecoder.Bc;
internal ref struct BitStream
{ {
public ulong low; private UInt128 value;
public ulong high;
public int ReadBits(int numBits) private readonly uint BottomBits
{ {
uint mask = (uint)((1 << numBits) - 1); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
// Read the low N bits get => unchecked((uint)this.value);
uint bits = (uint)(this.low & mask);
this.low >>= numBits;
// Put the low N bits of "high" into the high 64-N bits of "low".
this.low |= (this.high & mask) << ((sizeof(ulong) * 8) - numBits);
this.high >>= numBits;
return (int)bits;
} }
public int ReadBit() public BitStream(UInt128 value)
{ {
return this.ReadBits(1); this.value = value;
}
public BitStream(ReadOnlySpan<byte> span) : this(BinaryPrimitives.ReadUInt128LittleEndian(span))
{
}
public BitStream(ulong low, ulong high)
{
value = new UInt128(high, low);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public readonly uint PeakBits(int numBits)
{
uint mask = (1u << numBits) - 1u;
uint bits = BottomBits & mask;
return bits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void Advance(int numBits)
{
this.value >>= numBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public uint ReadBits(int numBits)
{
uint bits = PeakBits(numBits);
Advance(numBits);
return bits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public uint ReadBit()
{
uint result = BottomBits & 1u;
Advance(1);
return result;
} }
/// <summary> /// <summary>
@@ -29,17 +61,11 @@ internal struct BitStream
/// </summary> /// </summary>
/// <param name="numBits"></param> /// <param name="numBits"></param>
/// <returns></returns> /// <returns></returns>
public int ReadBitsReversed(int numBits) [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public uint ReadBitsReversed(int numBits)
{ {
int bits = this.ReadBits(numBits); uint bits = this.ReadBits(numBits);
// Reverse the bits. // Reverse the bits.
int result = 0; return bits.ReverseBits() >> (32 - numBits);
while (numBits-- != 0)
{
result <<= 1;
result |= bits & 1;
bits >>= 1;
}
return result;
} }
} }
@@ -0,0 +1,24 @@
namespace AssetRipper.TextureDecoder.Bc;
internal static class NumericExtensions
{
public static uint ReverseBits(this uint n)
{
n = (n >> 1) & 0x55555555 | (n << 1) & 0xAAAAAAAA;
n = (n >> 2) & 0x33333333 | (n << 2) & 0xCCCCCCCC;
n = (n >> 4) & 0x0F0F0F0F | (n << 4) & 0xF0F0F0F0;
n = (n >> 8) & 0x00FF00FF | (n << 8) & 0xFF00FF00;
n = (n >> 16) & 0x0000FFFF | (n << 16) & 0xFFFF0000;
return n;
}
public static uint ToUnsigned(this int value)
{
return unchecked((uint)value);
}
public static int ToSigned(this uint value)
{
return unchecked((int)value);
}
}
+3 -78
View File
@@ -6,32 +6,6 @@ namespace AssetRipper.TextureDecoder.Dxt
{ {
public static class DxtDecoder 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> /// <summary>
/// Decompress a DXT1 image /// Decompress a DXT1 image
/// </summary> /// </summary>
@@ -64,6 +38,7 @@ namespace AssetRipper.TextureDecoder.Dxt
where TOutputChannelValue : unmanaged where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue> where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{ {
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT1<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output)); return DecompressDXT1<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
} }
@@ -127,32 +102,6 @@ namespace AssetRipper.TextureDecoder.Dxt
return offset; 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> /// <summary>
/// Decompress a DXT3 image /// Decompress a DXT3 image
/// </summary> /// </summary>
@@ -185,6 +134,7 @@ namespace AssetRipper.TextureDecoder.Dxt
where TOutputChannelValue : unmanaged where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue> where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{ {
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT3<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output)); return DecompressDXT3<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
} }
@@ -258,32 +208,6 @@ namespace AssetRipper.TextureDecoder.Dxt
return offset; 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> /// <summary>
/// Decompress a DXT5 image /// Decompress a DXT5 image
/// </summary> /// </summary>
@@ -316,6 +240,7 @@ namespace AssetRipper.TextureDecoder.Dxt
where TOutputChannelValue : unmanaged where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue> where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{ {
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT5<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output)); return DecompressDXT5<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
} }
+233 -133
View File
@@ -1,15 +1,31 @@
using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Etc namespace AssetRipper.TextureDecoder.Etc
{ {
public static class EtcDecoder 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]; 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; int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength) if (input.Length < requiredLength)
{ {
@@ -19,22 +35,22 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4; int bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16]; Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 8) 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 clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16; int outputOffset = t * 4 * width + s * 4;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset)); Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{ {
for (int j = 0; j < clen; j++) 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; 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]; 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; int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength) if (input.Length < requiredLength)
{ {
@@ -59,22 +88,22 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4; int bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16]; Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 8) 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 clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16; int outputOffset = t * 4 * width + s * 4;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset)); Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{ {
for (int j = 0; j < clen; j++) 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; 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]; 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; int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength) if (input.Length < requiredLength)
{ {
@@ -99,22 +141,22 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4; int bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16]; Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 8) 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 clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16; int outputOffset = t * 4 * width + s * 4;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset)); Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{ {
for (int j = 0; j < clen; j++) 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; 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]; 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; int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
if (input.Length < requiredLength) if (input.Length < requiredLength)
{ {
@@ -139,23 +194,23 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4; int bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; int clen_last = (width + 3) % 4 + 1;
Span<uint> buf = stackalloc uint[16]; Span<ColorRGBA<byte>> buffer = stackalloc ColorRGBA<byte>[16];
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 16) for (int s = 0; s < bcw; s++, inputOffset += 16)
{ {
DecodeEtc2Block(input.Slice(inputOffset + 8, 8), buf); DecodeEtc2Block(input.Slice(inputOffset + 8, 8), buffer);
DecodeEtc2a8Block(input.Slice(inputOffset + 0, 8), buf); DecodeEtc2a8Block(input.Slice(inputOffset + 0, 8), buffer);
int clen = s < bcw - 1 ? 4 : clen_last; int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16; int outputOffset = t * 4 * width + s * 4;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset)); Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{ {
for (int j = 0; j < clen; j++) 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; 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]; 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; int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength) if (input.Length < requiredLength)
{ {
@@ -180,26 +248,26 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4; int bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; 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++) for (int i = 0; i < 16; i++)
{ {
buf[i] = 0xFF000000; buffer[i] = ColorRGBA<byte>.Black;
} }
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 8) 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 clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16; int outputOffset = t * 4 * width + s * 4;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset)); Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{ {
for (int j = 0; j < clen; j++) 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; 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]; 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; int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength) if (input.Length < requiredLength)
{ {
@@ -224,26 +305,26 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4; int bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; 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++) for (int i = 0; i < 16; i++)
{ {
buf[i] = 0xFF000000; buffer[i] = ColorRGBA<byte>.Black;
} }
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 8) 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 clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16; int outputOffset = t * 4 * width + s * 4;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset)); Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{ {
for (int j = 0; j < clen; j++) 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; 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]; 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 bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
@@ -268,27 +362,27 @@ namespace AssetRipper.TextureDecoder.Etc
} }
int clen_last = (width + 3) % 4 + 1; 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++) for (int i = 0; i < 16; i++)
{ {
buf[i] = 0xFF000000; buffer[i] = ColorRGBA<byte>.Black;
} }
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 16) for (int s = 0; s < bcw; s++, inputOffset += 16)
{ {
DecodeEacUnsignedBlock(input.Slice(inputOffset + 0, 8), buf, 2); DecodeEacUnsignedBlock(input.Slice(inputOffset + 0, 8), buffer, 0);
DecodeEacUnsignedBlock(input.Slice(inputOffset + 8, 8), buf, 1); DecodeEacUnsignedBlock(input.Slice(inputOffset + 8, 8), buffer, 1);
int clen = s < bcw - 1 ? 4 : clen_last; int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16; int outputOffset = t * 4 * width + s * 4;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset)); Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{ {
for (int j = 0; j < clen; j++) 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; 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]; 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; int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
if (input.Length < requiredLength) if (input.Length < requiredLength)
{ {
@@ -313,27 +420,27 @@ namespace AssetRipper.TextureDecoder.Etc
int bcw = (width + 3) / 4; int bcw = (width + 3) / 4;
int bch = (height + 3) / 4; int bch = (height + 3) / 4;
int clen_last = (width + 3) % 4 + 1; 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++) for (int i = 0; i < 16; i++)
{ {
buf[i] = 0xFF000000; buffer[i] = ColorRGBA<byte>.Black;
} }
int inputOffset = 0; int inputOffset = 0;
for (int t = 0; t < bch; t++) for (int t = 0; t < bch; t++)
{ {
for (int s = 0; s < bcw; s++, inputOffset += 16) for (int s = 0; s < bcw; s++, inputOffset += 16)
{ {
DecodeEacSignedBlock(input.Slice(inputOffset + 0, 8), buf, 2); DecodeEacSignedBlock(input.Slice(inputOffset + 0, 8), buffer, 0);
DecodeEacSignedBlock(input.Slice(inputOffset + 8, 8), buf, 1); DecodeEacSignedBlock(input.Slice(inputOffset + 8, 8), buffer, 1);
int clen = s < bcw - 1 ? 4 : clen_last; int clen = s < bcw - 1 ? 4 : clen_last;
int outputOffset = t * 16 * width + s * 16; int outputOffset = t * 4 * width + s * 4;
Span<uint> outputPtr = MemoryMarshal.Cast<byte, uint>(output.Slice(outputOffset)); Span<TOutputColor> outputPtr = output.Slice(outputOffset);
for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++) for (int i = 0, y = t * 4; i < 4 && y < height; i++, y++)
{ {
for (int j = 0; j < clen; j++) 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> /// <summary>
/// Parses an 8-byte Etc1 block from <paramref name="input"/>. /// Parses an 8-byte Etc1 block from <paramref name="input"/>.
/// </summary> /// </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]; byte i3 = input[3];
ReadOnlySpan<int> code = ReadOnlySpan<int> code =
@@ -393,13 +500,12 @@ namespace AssetRipper.TextureDecoder.Etc
int index = k << 1 & 2 | j & 1; int index = k << 1 & 2 | j & 1;
int cd = code[s]; int cd = code[s];
int m = Etc1ModifierTable[cd + index]; int m = Etc1ModifierTable[cd + index];
uint color = ApplicateColor(c, s, m);
int oi = WriteOrderTable[i]; 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 j = input[6] << 8 | input[7];
int k = input[4] << 8 | input[5]; int k = input[4] << 8 | input[5];
@@ -423,7 +529,7 @@ namespace AssetRipper.TextureDecoder.Etc
} }
int ti = input[3] >> 1 & 6 | input[3] & 1; int ti = input[3] >> 1 & 6 | input[3] & 1;
byte d = Etc2DistanceTable[ti]; byte d = Etc2DistanceTable[ti];
ReadOnlySpan<uint> color_set = ReadOnlySpan<ColorRGBA<byte>> color_set =
[ [
ApplicateColorRaw(c, 0), ApplicateColorRaw(c, 0),
ApplicateColor(c, 1, d), ApplicateColor(c, 1, d),
@@ -434,9 +540,8 @@ namespace AssetRipper.TextureDecoder.Etc
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{ {
int index = k << 1 & 2 | j & 1; int index = k << 1 & 2 | j & 1;
uint color = color_set[index];
int oi = WriteOrderTable[i]; int oi = WriteOrderTable[i];
output[oi] = color; output[oi] = color_set[index];
} }
} }
else else
@@ -464,7 +569,7 @@ namespace AssetRipper.TextureDecoder.Etc
++di; ++di;
} }
byte d = Etc2DistanceTable[di]; byte d = Etc2DistanceTable[di];
ReadOnlySpan<uint> color_set = ReadOnlySpan<ColorRGBA<byte>> color_set =
[ [
ApplicateColor(c, 0, d), ApplicateColor(c, 0, d),
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) for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{ {
int index = k << 1 & 2 | j & 1; int index = k << 1 & 2 | j & 1;
uint color = color_set[index];
int oi = WriteOrderTable[i]; int oi = WriteOrderTable[i];
output[oi] = color; output[oi] = color_set[index];
} }
} }
else 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 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 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); 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 index = k << 1 & 2 | j & 1;
int ci = code[s]; int ci = code[s];
int m = Etc1ModifierTable[ci + index]; int m = Etc1ModifierTable[ci + index];
uint color = ApplicateColor(c, s, m);
int oi = WriteOrderTable[i]; 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 index = k << 1 & 2 | j & 1;
int ci = code[s]; int ci = code[s];
int m = Etc1ModifierTable[ci + index]; int m = Etc1ModifierTable[ci + index];
uint color = ApplicateColor(c, s, m);
int oi = WriteOrderTable[i]; int oi = WriteOrderTable[i];
output[oi] = color; output[oi] = ApplicateColor(c, s, m);
} }
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [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) 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 j = input[6] << 8 | input[7];
int k = input[4] << 8 | input[5]; int k = input[4] << 8 | input[5];
@@ -612,7 +714,7 @@ namespace AssetRipper.TextureDecoder.Etc
} }
int ti = input[3] >> 1 & 6 | input[3] & 1; int ti = input[3] >> 1 & 6 | input[3] & 1;
byte d = Etc2DistanceTable[ti]; byte d = Etc2DistanceTable[ti];
ReadOnlySpan<uint> color_set = ReadOnlySpan<ColorRGBA<byte>> color_set =
[ [
ApplicateColorRaw(c, 0), ApplicateColorRaw(c, 0),
ApplicateColor(c, 1, d), ApplicateColor(c, 1, d),
@@ -623,10 +725,8 @@ namespace AssetRipper.TextureDecoder.Etc
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{ {
int index = k << 1 & 2 | j & 1; int index = k << 1 & 2 | j & 1;
uint color = color_set[index];
uint mask = PunchthroughMaskTable[index];
int oi = WriteOrderTable[i]; int oi = WriteOrderTable[i];
output[oi] = color & mask; output[oi] = PunchthroughBooleanTable[index] ? color_set[index] : default;
} }
} }
else else
@@ -654,7 +754,7 @@ namespace AssetRipper.TextureDecoder.Etc
++di; ++di;
} }
byte d = Etc2DistanceTable[di]; byte d = Etc2DistanceTable[di];
ReadOnlySpan<uint> color_set = ReadOnlySpan<ColorRGBA<byte>> color_set =
[ [
ApplicateColor(c, 0, d), ApplicateColor(c, 0, d),
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) for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{ {
int index = k << 1 & 2 | j & 1; int index = k << 1 & 2 | j & 1;
uint color = color_set[index];
uint mask = PunchthroughMaskTable[index];
int oi = WriteOrderTable[i]; int oi = WriteOrderTable[i];
output[oi] = color & mask; output[oi] = PunchthroughBooleanTable[index] ? color_set[index] : default;
} }
} }
else 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 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 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); 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) for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
{ {
int s = Etc1SubblockTable[ti + i];
int index = k << 1 & 2 | j & 1; 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]; 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 @base = input[0];
int data1 = input[1]; int data1 = input[1];
int mul = data1 >> 4; int mul = data1 >> 4;
@@ -748,48 +852,50 @@ namespace AssetRipper.TextureDecoder.Etc
{ {
for (int i = 0; i < 16; i++) 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 else
{ {
int table = data1 & 0xF; int table = data1 & 0xF;
ulong l = Get6SwapedBytes(input); 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 @base = input[0];
int data1 = input[1]; int data1 = input[1];
int mul = data1 >> 4; int mul = data1 >> 4;
if (mul == 0) if (mul == 0)
{ {
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base); DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base);
} }
else else
{ {
int table = data1 & 0xF; int table = data1 & 0xF;
ulong l = Get6SwapedBytes(input); 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 @base = 127 + unchecked((sbyte)input[0]);
int data1 = input[1]; int data1 = input[1];
int mul = data1 >> 4; int mul = data1 >> 4;
if (mul == 0) if (mul == 0)
{ {
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base); DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base);
} }
else else
{ {
int table = data1 & 0xF; int table = data1 & 0xF;
ulong l = Get6SwapedBytes(input); ulong l = Get6SwapedBytes(input);
DecodeEac11Block(MemoryMarshal.Cast<uint, byte>(output).Slice(channel), @base, table, mul, l); DecodeEac11Block(MemoryMarshal.Cast<ColorRGBA<byte>, byte>(output).Slice(channel), @base, table, mul, l);
} }
} }
@@ -805,7 +911,7 @@ namespace AssetRipper.TextureDecoder.Etc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void DecodeEac11Block(Span<byte> output, int @base, int ti, int mul, ulong l) private static void DecodeEac11Block(Span<byte> output, int @base, int ti, int mul, ulong l)
{ {
ReadOnlySpan<sbyte> table = Etc2AlphaModTable.AsSpan(ti * 8, 8); ReadOnlySpan<sbyte> table = Etc2AlphaModTable.Slice(ti * 8, 8);
for (int i = 0; i < 16; i++, l >>= 3) for (int i = 0; i < 16; i++, l >>= 3)
{ {
int val = @base + mul * table[unchecked((int)(l & 0b111))]; int val = @base + mul * table[unchecked((int)(l & 0b111))];
@@ -813,12 +919,6 @@ namespace AssetRipper.TextureDecoder.Etc
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static uint Color(int r, int g, int b, int a)
{
return unchecked((uint)(r << 16 | g << 8 | b | a << 24));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static byte Clamp255(int n) private static byte Clamp255(int n)
{ {
@@ -826,21 +926,21 @@ namespace AssetRipper.TextureDecoder.Etc
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [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)] [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)] [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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -849,14 +949,14 @@ namespace AssetRipper.TextureDecoder.Etc
return data[7] | (uint)data[6] << 8 | (uint)data[5] << 16 | (uint)data[4] << 24 | (ulong)data[3] << 32 | (ulong)data[2] << 40; return data[7] | (uint)data[6] << 8 | (uint)data[5] << 16 | (uint)data[4] << 24 | (ulong)data[3] << 32 | (ulong)data[2] << 40;
} }
private static readonly byte[] WriteOrderTable = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15]; private static ReadOnlySpan<byte> WriteOrderTable => [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
private static readonly byte[] WriteOrderTableRev = [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0]; private static ReadOnlySpan<byte> WriteOrderTableRev => [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0];
private static readonly int[] Etc1SubblockTable = private static ReadOnlySpan<int> Etc1SubblockTable =>
[ [
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
]; ];
private static readonly int[] Etc1ModifierTable = private static ReadOnlySpan<int> Etc1ModifierTable =>
[ [
2, 8, -2, -8, 2, 8, -2, -8,
5, 17, -5, -17, 5, 17, -5, -17,
@@ -867,7 +967,7 @@ namespace AssetRipper.TextureDecoder.Etc
33, 106, -33, -106, 33, 106, -33, -106,
47, 183, -47, -183, 47, 183, -47, -183,
]; ];
private static readonly int[] PunchthroughModifierTable = private static ReadOnlySpan<int> PunchthroughModifierTable =>
[ [
0, 8, 0, -8, 0, 8, 0, -8,
0, 17, 0, -17, 0, 17, 0, -17,
@@ -878,8 +978,8 @@ namespace AssetRipper.TextureDecoder.Etc
0, 106, 0, -106, 0, 106, 0, -106,
0, 183, 0, -183, 0, 183, 0, -183,
]; ];
private static readonly byte[] Etc2DistanceTable = [3, 6, 11, 16, 23, 32, 41, 64]; private static ReadOnlySpan<byte> Etc2DistanceTable => [3, 6, 11, 16, 23, 32, 41, 64];
private static readonly sbyte[] Etc2AlphaModTable = private static ReadOnlySpan<sbyte> Etc2AlphaModTable =>
[ [
-3, -6, -9, -15, 2, 5, 8, 14, -3, -6, -9, -15, 2, 5, 8, 14,
-3, -7, -10, -13, 2, 6, 9, 12, -3, -7, -10, -13, 2, 6, 9, 12,
@@ -898,6 +998,6 @@ namespace AssetRipper.TextureDecoder.Etc
-4, -6, -8, -9, 3, 5, 7, 8, -4, -6, -8, -9, 3, 5, 7, 8,
-3, -5, -7, -9, 2, 4, 6, 8, -3, -5, -7, -9, 2, 4, 6, 8,
]; ];
private static readonly uint[] PunchthroughMaskTable = [0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF]; private static ReadOnlySpan<bool> PunchthroughBooleanTable => [true, true, false, true];
} }
} }
@@ -76,16 +76,16 @@ internal static class BinaryWriterExtensions
switch (Unsafe.SizeOf<T>()) switch (Unsafe.SizeOf<T>())
{ {
case sizeof(byte): case sizeof(byte):
writer.Write(Unsafe.As<T, byte>(ref value)); writer.Write(Unsafe.BitCast<T, byte>(value));
break; break;
case sizeof(ushort): case sizeof(ushort):
writer.Write(Unsafe.As<T, ushort>(ref value)); writer.Write(Unsafe.BitCast<T, ushort>(value));
break; break;
case sizeof(uint): case sizeof(uint):
writer.Write(Unsafe.As<T, uint>(ref value)); writer.Write(Unsafe.BitCast<T, uint>(value));
break; break;
case sizeof(ulong): case sizeof(ulong):
writer.Write(Unsafe.As<T, ulong>(ref value)); writer.Write(Unsafe.BitCast<T, ulong>(value));
break; break;
} }
} }
@@ -4,7 +4,7 @@ public readonly record struct ExrChannelList(ExrChannel[] Channels) : IExrDataTy
{ {
public static ReadOnlySpan<byte> TypeName => "chlist"u8; public static ReadOnlySpan<byte> TypeName => "chlist"u8;
public int Size => Channels.Sum(c => c.Size); public int Size => Channels.Sum(c => c.Size) + sizeof(byte);
public void Write(BinaryWriter writer) public void Write(BinaryWriter writer)
{ {
+1 -1
View File
@@ -75,7 +75,7 @@ public static class ExrWriter
for (int i = 0; i < height; i++) for (int i = 0; i < height; i++)
{ {
writer.Write(offset); writer.Write(offset);
offset += sizeof(int) + ChannelCount * ChannelSize * width; offset += sizeof(int) + sizeof(int) + ChannelCount * ChannelSize * width;
} }
//Scan lines //Scan lines
@@ -1,38 +1,65 @@
//#define DISABLE_TWINDDLING_ROUTINE using AssetRipper.TextureDecoder.Rgb;
#define ASSUME_IMAGE_TILING using System.Diagnostics;
namespace AssetRipper.TextureDecoder.Pvrtc namespace AssetRipper.TextureDecoder.Pvrtc
{ {
public static partial class PvrtcDecoder public static partial class PvrtcDecoder
{ {
/// <summary> /// <summary>
/// Decompresses PVRTC to RGBA 8888 /// Decompresses PVRTC
/// </summary> /// </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="input">The PVRTC texture data to decompress</param>
/// <param name="xDim">X dimension (width) of the texture</param> /// <param name="xDim">X dimension (width) of the texture</param>
/// <param name="yDim">Y dimension (height) of the texture</param> /// <param name="yDim">Y dimension (height) of the texture</param>
/// <param name="output">The decompressed texture data</param> /// <param name="output">The decompressed texture data</param>
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param> /// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
/// <returns>The number of bytes read from <paramref name="input"/></returns> /// <returns>The number of bytes read from <paramref name="input"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static int DecompressPVRTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, out byte[] output)
public static int DecompressPVRTC(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)]; output = new byte[xDim * yDim * Unsafe.SizeOf<TOutputColor>()];
return DecompressPVRTC(input, xDim, yDim, do2bitMode, output); return DecompressPVRTC<TOutputColor, TOutputChannelValue>(input, xDim, yDim, do2bitMode, output);
} }
/// <summary> /// <summary>
/// Decompresses PVRTC to RGBA 8888 /// Decompresses PVRTC
/// </summary> /// </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="input">The PVRTC texture data to decompress</param>
/// <param name="xDim">X dimension (width) of the texture</param> /// <param name="xDim">X dimension (width) of the texture</param>
/// <param name="yDim">Y dimension (height) of the texture</param> /// <param name="yDim">Y dimension (height) of the texture</param>
/// <param name="output">The decompressed texture data</param> /// <param name="output">The decompressed texture data</param>
/// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param> /// <param name="do2bitMode">Signifies whether the data is PVRTC2 or PVRTC4</param>
/// <returns>The number of bytes read from <paramref name="input"/></returns> /// <returns>The number of bytes read from <paramref name="input"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static int DecompressPVRTC<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<byte> output)
public static int DecompressPVRTC(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; int xBlockSize = do2bitMode ? BlockX2bpp : BlockX4bpp;
// for MBX don't allow the sizes to get too small // for MBX don't allow the sizes to get too small
int blockXDim = Math.Max(2, xDim / xBlockSize); int blockXDim = Math.Max(2, xDim / xBlockSize);
@@ -65,12 +92,12 @@ namespace AssetRipper.TextureDecoder.Pvrtc
int blockX = (x - xBlockSize / 2); int blockX = (x - xBlockSize / 2);
int blockY = (y - BlockYSize / 2); int blockY = (y - BlockYSize / 2);
blockX = LimitCoord(blockX, xDim) / xBlockSize; blockX = LimitCoordinate(blockX, xDim) / xBlockSize;
blockY = LimitCoord(blockY, yDim) / BlockYSize; blockY = LimitCoordinate(blockY, yDim) / BlockYSize;
// compute the positions of the other 3 blocks // compute the positions of the other 3 blocks
int blockXp1 = LimitCoord(blockX + 1, blockXDim); int blockXp1 = LimitCoordinate(blockX + 1, blockXDim);
int blockYp1 = LimitCoord(blockY + 1, blockYDim); int blockYp1 = LimitCoordinate(blockY + 1, blockYDim);
// map to block memory locations // map to block memory locations
uint blocki00 = TwiddleUV((uint)blockYDim, (uint)blockXDim, (uint)blockY, (uint)blockX); 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); 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); GetModulationValue(x, y, do2bitMode, modulationVals, modulationModes, out int mod, out bool doPT);
// compute the modulated color. Swap red and blue channel // compute the modulated color.
int position = (x + y * xDim) << 2; byte red = (byte)((aSig[0] * 8 + mod * (bSig[0] - aSig[0])) >> 3);
output[position + 0] = (byte)((aSig[2] * 8 + mod * (bSig[2] - aSig[2])) >> 3); byte green = (byte)((aSig[1] * 8 + mod * (bSig[1] - aSig[1])) >> 3);
output[position + 1] = (byte)((aSig[1] * 8 + mod * (bSig[1] - aSig[1])) >> 3); byte blue = (byte)((aSig[2] * 8 + mod * (bSig[2] - aSig[2])) >> 3);
output[position + 2] = (byte)((aSig[0] * 8 + mod * (bSig[0] - aSig[0])) >> 3); byte alpha = doPT ? (byte)0 : (byte)((aSig[3] * 8 + mod * (bSig[3] - aSig[3])) >> 3);
output[position + 3] = 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> /// <returns>The twiddled offset of the pixel</returns>
private static uint TwiddleUV(uint ySize, uint xSize, uint yPos, uint xPos) private static uint TwiddleUV(uint ySize, uint xSize, uint yPos, uint xPos)
{ {
#if DEBUG Debug.Assert(yPos < ySize);
if (yPos >= ySize) Debug.Assert(xPos < xSize);
{ Debug.Assert(BitOperations.IsPow2(ySize));
throw new Exception(); Debug.Assert(BitOperations.IsPow2(xSize));
}
if (xPos >= xSize)
{
throw new Exception();
}
if (!BitOperations.IsPow2(ySize))
{
throw new Exception();
}
if (!BitOperations.IsPow2(xSize))
{
throw new Exception();
}
#endif
uint minDimension; uint minDimension;
uint maxValue; uint maxValue;
@@ -164,10 +177,6 @@ namespace AssetRipper.TextureDecoder.Pvrtc
maxValue = yPos; 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 // step through all the bits in the "minimum" dimension
uint srcBitPos = 1; uint srcBitPos = 1;
uint dstBitPos = 1; uint dstBitPos = 1;
@@ -195,7 +204,6 @@ namespace AssetRipper.TextureDecoder.Pvrtc
maxValue >>= shiftCount; maxValue >>= shiftCount;
twiddled |= (maxValue << (2 * shiftCount)); twiddled |= (maxValue << (2 * shiftCount));
return twiddled; return twiddled;
#endif
} }
/// <summary> /// <summary>
@@ -217,36 +225,36 @@ namespace AssetRipper.TextureDecoder.Pvrtc
int modVal; int modVal;
if (modulationModes[y * 16 + x] == 0) if (modulationModes[y * 16 + x] == 0)
{ {
modVal = m_repVals0[modulationVals[y * 16 + x]]; modVal = RepVals0[modulationVals[y * 16 + x]];
} }
else if (do2bitMode) else if (do2bitMode)
{ {
// if this is a stored value // if this is a stored value
if (((x ^ y) & 1) == 0) if (((x ^ y) & 1) == 0)
{ {
modVal = m_repVals0[modulationVals[y * 16 + x]]; modVal = RepVals0[modulationVals[y * 16 + x]];
} }
// else average from the neighbours if H&V interpolation.. // else average from the neighbours if H&V interpolation..
else if (modulationModes[y * 16 + x] == 1) else if (modulationModes[y * 16 + x] == 1)
{ {
modVal = (m_repVals0[modulationVals[(y - 1) * 16 + x]] + m_repVals0[modulationVals[(y + 1) * 16 + x]] + modVal = (RepVals0[modulationVals[(y - 1) * 16 + x]] + RepVals0[modulationVals[(y + 1) * 16 + x]] +
m_repVals0[modulationVals[y * 16 + x - 1]] + m_repVals0[modulationVals[y * 16 + x + 1]] + 2) / 4; RepVals0[modulationVals[y * 16 + x - 1]] + RepVals0[modulationVals[y * 16 + x + 1]] + 2) / 4;
} }
// else if H-Only // else if H-Only
else if (modulationModes[y * 16 + x] == 2) else if (modulationModes[y * 16 + x] == 2)
{ {
modVal = (m_repVals0[modulationVals[y * 16 + x - 1]] + m_repVals0[modulationVals[y * 16 + x + 1]] + 1) / 2; modVal = (RepVals0[modulationVals[y * 16 + x - 1]] + RepVals0[modulationVals[y * 16 + x + 1]] + 1) / 2;
} }
// else it's V-Only // else it's V-Only
else else
{ {
modVal = (m_repVals0[modulationVals[(y - 1) * 16 + x]] + m_repVals0[modulationVals[(y + 1) * 16 + x]] + 1) / 2; modVal = (RepVals0[modulationVals[(y - 1) * 16 + x]] + RepVals0[modulationVals[(y + 1) * 16 + x]] + 1) / 2;
} }
} }
// else it's 4BPP and PT encoding // else it's 4BPP and PT encoding
else else
{ {
modVal = m_repVals1[modulationVals[y * 16 + x]]; modVal = RepVals1[modulationVals[y * 16 + x]];
doPT = modulationVals[y * 16 + x] == PTIndex; doPT = modulationVals[y * 16 + x] == PTIndex;
} }
@@ -290,16 +298,11 @@ namespace AssetRipper.TextureDecoder.Pvrtc
} }
} }
#if DEBUG
// sanity check // sanity check
for (int i = 0; i < 4; i++) Debug.Assert(result[0] < 256);
{ Debug.Assert(result[1] < 256);
if (result[i] >= 256) Debug.Assert(result[2] < 256);
{ Debug.Assert(result[3] < 256);
throw new Exception("Sanity failed");
}
}
#endif
// convert from 5554 to 8888, do RGB 5.3 => 8 // convert from 5554 to 8888, do RGB 5.3 => 8
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
@@ -308,16 +311,11 @@ namespace AssetRipper.TextureDecoder.Pvrtc
} }
result[3] += result[3] >> 4; result[3] += result[3] >> 4;
#if DEBUG
// 2nd sanity check // 2nd sanity check
for (int i = 0; i < 4; i++) Debug.Assert(result[0] < 256);
{ Debug.Assert(result[1] < 256);
if (result[i] >= 256) Debug.Assert(result[2] < 256);
{ Debug.Assert(result[3] < 256);
throw new Exception("2nd sanity failed");
}
}
#endif
} }
/// <summary> /// <summary>
@@ -456,19 +454,20 @@ namespace AssetRipper.TextureDecoder.Pvrtc
} }
/// <summary> /// <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> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static int LimitCoordinate(int value, int size, bool assumeImageTiling = true)
private static int LimitCoord(int value, int size)
{ {
#if ASSUME_IMAGE_TILING if (assumeImageTiling)
// wrap coord {
return value & (size - 1); // wrap coordinate
#else return value & (size - 1);
// clamp }
return h < x ? h : (x > l ? x : l); else
return Clamp(Val, 0, Size - 1); {
#endif // clamp coordinate
return int.Clamp(value, 0, size - 1);
}
} }
/// <summary> /// <summary>
@@ -487,7 +486,7 @@ namespace AssetRipper.TextureDecoder.Pvrtc
private const int BlockX2bpp = 8; private const int BlockX2bpp = 8;
private const int BlockX4bpp = 4; private const int BlockX4bpp = 4;
private static readonly int[] m_repVals0 = [0, 3, 5, 8]; private static ReadOnlySpan<int> RepVals0 => [0, 3, 5, 8];
private static readonly int[] m_repVals1 = [0, 4, 4, 8]; private static ReadOnlySpan<int> RepVals1 => [0, 4, 4, 8];
} }
} }
+27 -1
View File
@@ -82,7 +82,7 @@ public static class Color
{ {
if (typeof(TThis) == typeof(TTarget)) if (typeof(TThis) == typeof(TTarget))
{ {
return Unsafe.As<TThis, TTarget>(ref color); return Unsafe.BitCast<TThis, TTarget>(color);
} }
else else
{ {
@@ -92,4 +92,30 @@ public static class Color
return destination; return destination;
} }
} }
internal static TColor SRgbToLinear<TColor, TChannel>(this TColor color)
where TChannel : unmanaged
where TColor : unmanaged, IColor<TChannel>
{
color.GetChannels(out TChannel r, out TChannel g, out TChannel b, out TChannel a);
color.SetChannels(
SRgb.ToLinear(r),
SRgb.ToLinear(g),
SRgb.ToLinear(b),
a);
return color;
}
internal static TColor LinearToSRgb<TColor, TChannel>(this TColor color)
where TChannel : unmanaged
where TColor : unmanaged, IColor<TChannel>
{
color.GetChannels(out TChannel r, out TChannel g, out TChannel b, out TChannel a);
color.SetChannels(
SRgb.FromLinear(r),
SRgb.FromLinear(g),
SRgb.FromLinear(b),
a);
return color;
}
} }
@@ -4,6 +4,8 @@ namespace AssetRipper.TextureDecoder.Rgb.Formats;
public partial struct ColorA<T> : IColor<ColorA<T>, T> where T : unmanaged, INumberBase<T>, IMinMaxValue<T> public partial struct ColorA<T> : IColor<ColorA<T>, T> where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{ {
public T A { get; set; }
public readonly T R public readonly T R
{ {
get => NumericConversion.GetMinimumValueSafe<T>(); get => NumericConversion.GetMinimumValueSafe<T>();
@@ -22,8 +24,6 @@ public partial struct ColorA<T> : IColor<ColorA<T>, T> where T : unmanaged, INum
set { } set { }
} }
public T A { get; set; }
public ColorA(T a) public ColorA(T a)
{ {
A = a; A = a;
@@ -0,0 +1,53 @@
// Auto-generated code. Do not modify manually.
namespace AssetRipper.TextureDecoder.Rgb.Formats;
public partial struct ColorARGB<T> : IColor<ColorARGB<T>, T> where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
public T A { get; set; }
public T R { get; set; }
public T G { get; set; }
public T B { get; set; }
public ColorARGB(T r, T g, T b, T a)
{
R = r;
G = g;
B = b;
A = a;
}
public readonly void GetChannels(out T r, out T g, out T b, out T a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(T r, T g, T b, T a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => true;
static bool IColor.HasGreenChannel => true;
static bool IColor.HasBlueChannel => true;
static bool IColor.HasAlphaChannel => true;
static bool IColor.ChannelsAreFullyUtilized => true;
static Type IColor.ChannelType => typeof(T);
public static ColorARGB<T> Black => new(NumericConversion.GetMinimumValueSafe<T>(), NumericConversion.GetMinimumValueSafe<T>(), NumericConversion.GetMinimumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>());
public static ColorARGB<T> White => new(NumericConversion.GetMaximumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>());
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
@@ -1,20 +0,0 @@
namespace AssetRipper.TextureDecoder.Rgb.Formats
{
public partial struct ColorARGB32 : IColor<byte>
{
public byte A { get; set; }
public byte R { get; set; }
public byte G { get; set; }
public byte B { get; set; }
public readonly void GetChannels(out byte r, out byte g, out byte b, out byte a)
{
DefaultColorMethods.GetChannels(this, out r, out g, out b, out a);
}
public void SetChannels(byte r, byte g, byte b, byte a)
{
DefaultColorMethods.SetChannels(ref this, r, g, b, a);
}
}
}
@@ -1,19 +0,0 @@
// Auto-generated code. Do not modify manually.
namespace AssetRipper.TextureDecoder.Rgb.Formats
{
public partial struct ColorARGB32 : IColor<byte>
{
static bool IColor.HasRedChannel => true;
static bool IColor.HasGreenChannel => true;
static bool IColor.HasBlueChannel => true;
static bool IColor.HasAlphaChannel => true;
static bool IColor.ChannelsAreFullyUtilized => true;
static Type IColor.ChannelType => typeof(byte);
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
}
@@ -0,0 +1,55 @@
// Auto-generated code. Do not modify manually.
namespace AssetRipper.TextureDecoder.Rgb.Formats;
public partial struct ColorBGR<T> : IColor<ColorBGR<T>, T> where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
public T B { get; set; }
public T G { get; set; }
public T R { get; set; }
public readonly T A
{
get => NumericConversion.GetMaximumValueSafe<T>();
set { }
}
public ColorBGR(T r, T g, T b)
{
R = r;
G = g;
B = b;
}
public readonly void GetChannels(out T r, out T g, out T b, out T a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(T r, T g, T b, T a)
{
R = r;
G = g;
B = b;
}
static bool IColor.HasRedChannel => true;
static bool IColor.HasGreenChannel => true;
static bool IColor.HasBlueChannel => true;
static bool IColor.HasAlphaChannel => false;
static bool IColor.ChannelsAreFullyUtilized => true;
static Type IColor.ChannelType => typeof(T);
public static ColorBGR<T> Black => new(NumericConversion.GetMinimumValueSafe<T>(), NumericConversion.GetMinimumValueSafe<T>(), NumericConversion.GetMinimumValueSafe<T>());
public static ColorBGR<T> White => new(NumericConversion.GetMaximumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>());
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B} }}";
}
}
@@ -0,0 +1,53 @@
// Auto-generated code. Do not modify manually.
namespace AssetRipper.TextureDecoder.Rgb.Formats;
public partial struct ColorBGRA<T> : IColor<ColorBGRA<T>, T> where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
public T B { get; set; }
public T G { get; set; }
public T R { get; set; }
public T A { get; set; }
public ColorBGRA(T r, T g, T b, T a)
{
R = r;
G = g;
B = b;
A = a;
}
public readonly void GetChannels(out T r, out T g, out T b, out T a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(T r, T g, T b, T a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => true;
static bool IColor.HasGreenChannel => true;
static bool IColor.HasBlueChannel => true;
static bool IColor.HasAlphaChannel => true;
static bool IColor.ChannelsAreFullyUtilized => true;
static Type IColor.ChannelType => typeof(T);
public static ColorBGRA<T> Black => new(NumericConversion.GetMinimumValueSafe<T>(), NumericConversion.GetMinimumValueSafe<T>(), NumericConversion.GetMinimumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>());
public static ColorBGRA<T> White => new(NumericConversion.GetMaximumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>(), NumericConversion.GetMaximumValueSafe<T>());
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
@@ -1,20 +0,0 @@
namespace AssetRipper.TextureDecoder.Rgb.Formats
{
public partial struct ColorBGRA32 : IColor<byte>
{
public byte B { get; set; }
public byte G { get; set; }
public byte R { get; set; }
public byte A { get; set; }
public readonly void GetChannels(out byte r, out byte g, out byte b, out byte a)
{
DefaultColorMethods.GetChannels(this, out r, out g, out b, out a);
}
public void SetChannels(byte r, byte g, byte b, byte a)
{
DefaultColorMethods.SetChannels(ref this, r, g, b, a);
}
}
}
@@ -1,19 +0,0 @@
// Auto-generated code. Do not modify manually.
namespace AssetRipper.TextureDecoder.Rgb.Formats
{
public partial struct ColorBGRA32 : IColor<byte>
{
static bool IColor.HasRedChannel => true;
static bool IColor.HasGreenChannel => true;
static bool IColor.HasBlueChannel => true;
static bool IColor.HasAlphaChannel => true;
static bool IColor.ChannelsAreFullyUtilized => true;
static Type IColor.ChannelType => typeof(byte);
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
}
@@ -42,7 +42,7 @@ public static partial class NumericConversion
TFrom SignBit = TFrom.One << (Unsafe.SizeOf<TFrom>() * 8 - 1); TFrom SignBit = TFrom.One << (Unsafe.SizeOf<TFrom>() * 8 - 1);
TFrom converted = (SignBit ^ value); TFrom converted = (SignBit ^ value);
return Unsafe.As<TFrom, TTo>(ref converted); return Unsafe.BitCast<TFrom, TTo>(converted);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -56,6 +56,6 @@ public static partial class NumericConversion
} }
TTo SignBit = TTo.One << (Unsafe.SizeOf<TTo>() * 8 - 1); TTo SignBit = TTo.One << (Unsafe.SizeOf<TTo>() * 8 - 1);
return SignBit ^ Unsafe.As<TFrom, TTo>(ref value); return SignBit ^ Unsafe.BitCast<TFrom, TTo>(value);
} }
} }
File diff suppressed because it is too large. Load diff
+42 -503
View File
@@ -1,490 +1,7 @@
using System.Buffers.Binary;
namespace AssetRipper.TextureDecoder.Rgb namespace AssetRipper.TextureDecoder.Rgb
{ {
public static class RgbConverter public static class RgbConverter
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int A8ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return A8ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int A8ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = 0; // b
output[oo + 1] = 0; // g
output[oo + 2] = 0; // r
output[oo + 3] = input[io]; // a
oo += 4;
io++;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB24ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGB24ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB24ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 2]; // b
output[oo + 1] = input[io + 1]; // g
output[oo + 2] = input[io + 0]; // r
output[oo + 3] = 255; // a
io += 3;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBA32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGBA32ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBA32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 2]; // b
output[oo + 1] = input[io + 1]; // g
output[oo + 2] = input[io + 0]; // r
output[oo + 3] = input[io + 3]; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int ARGB32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return ARGB32ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int ARGB32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 3]; // b
output[oo + 1] = input[io + 2]; // g
output[oo + 2] = input[io + 1]; // r
output[oo + 3] = input[io + 0]; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB16ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGB16ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB16ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = (byte)(input[io + 1] & 0xF8);
byte g = unchecked((byte)((input[io + 1] << 5) | ((input[io + 0] & 0xE0) >> 3)));
byte b = unchecked((byte)(input[io + 0] << 3));
output[oo + 0] = b; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 2;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RG16ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RG16ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RG16ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = 0; // b
output[oo + 1] = input[io + 1]; // g
output[oo + 2] = input[io + 0]; // r
output[oo + 3] = 255; // a
io += 2;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int R8ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return R8ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int R8ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = 0; // b
output[oo + 1] = 0; // g
output[oo + 2] = input[io + 0]; // r
output[oo + 3] = 255; // a
io += 1;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RHalfToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToHalf(input, io) * 255f);
output[oo + 0] = 0; // b
output[oo + 1] = 0; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 2;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGHalfToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToHalf(input, io + 0) * 255f);
byte g = ClampByte(ToHalf(input, io + 2) * 255f);
output[oo + 0] = 0; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBAHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGBAHalfToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBAHalfToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToHalf(input, io + 0) * 255f);
byte g = ClampByte(ToHalf(input, io + 2) * 255f);
byte b = ClampByte(ToHalf(input, io + 4) * 255f);
byte a = ClampByte(ToHalf(input, io + 6) * 255f);
output[oo + 0] = b; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = a; // a
io += 8;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RFloatToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToSingle(input, io) * 255f);
output[oo + 0] = 0; // b
output[oo + 1] = 0; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGFloatToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToSingle(input, io + 0) * 255f);
byte g = ClampByte(ToSingle(input, io + 4) * 255f);
output[oo + 0] = 0; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 8;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBAFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGBAFloatToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBAFloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
byte r = ClampByte(ToSingle(input, io + 0) * 255f);
byte g = ClampByte(ToSingle(input, io + 4) * 255f);
byte b = ClampByte(ToSingle(input, io + 8) * 255f);
byte a = ClampByte(ToSingle(input, io + 12) * 255f);
output[oo + 0] = b; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = a; // a
io += 16;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB9e5FloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGB9e5FloatToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB9e5FloatToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
uint value = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(io, 4));
double scale = Math.Pow(2, unchecked((int)(value >> 27) - 24));
byte r = ClampByte((value >> 0 & 0x1FF) * scale * 255.0);
byte g = ClampByte((value >> 9 & 0x1FF) * scale * 255.0);
byte b = ClampByte((value >> 18 & 0x1FF) * scale * 255.0);
output[oo + 0] = b; // b
output[oo + 1] = g; // g
output[oo + 2] = r; // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RG32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RG32ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RG32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = 0; // b
output[oo + 1] = input[io + 3]; // g
output[oo + 2] = input[io + 1]; // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB48ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGB48ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGB48ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 5]; // b
output[oo + 1] = input[io + 3]; // g
output[oo + 2] = input[io + 1]; // r
output[oo + 3] = 255; // a
io += 6;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBA64ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
output = new byte[width * height * 4];
return RGBA64ToBGRA32(input, width, height, output);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int RGBA64ToBGRA32(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int io = 0;
int oo = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
output[oo + 0] = input[io + 5]; // b
output[oo + 1] = input[io + 3]; // g
output[oo + 2] = input[io + 1]; // r
output[oo + 3] = input[io + 7]; // a
io += 8;
oo += 4;
}
}
return io;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>(ReadOnlySpan<byte> input, int width, int height, out byte[] output) public static int Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TSourceChannel : unmanaged where TSourceChannel : unmanaged
@@ -503,6 +20,7 @@ namespace AssetRipper.TextureDecoder.Rgb
where TDestinationChannel : unmanaged where TDestinationChannel : unmanaged
where TDestinationColor : unmanaged, IColor<TDestinationChannel> where TDestinationColor : unmanaged, IColor<TDestinationChannel>
{ {
ThrowHelper.ThrowIfNotLittleEndian();
ReadOnlySpan<TSourceColor> sourceSpan = MemoryMarshal.Cast<byte, TSourceColor>(input).Slice(0, width * height); ReadOnlySpan<TSourceColor> sourceSpan = MemoryMarshal.Cast<byte, TSourceColor>(input).Slice(0, width * height);
Span<TDestinationColor> destinationSpan = MemoryMarshal.Cast<byte, TDestinationColor>(output).Slice(0, width * height); Span<TDestinationColor> destinationSpan = MemoryMarshal.Cast<byte, TDestinationColor>(output).Slice(0, width * height);
Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>(sourceSpan, destinationSpan); Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>(sourceSpan, destinationSpan);
@@ -516,34 +34,55 @@ namespace AssetRipper.TextureDecoder.Rgb
where TDestinationChannel : unmanaged where TDestinationChannel : unmanaged
where TDestinationColor : unmanaged, IColor<TDestinationChannel> 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));
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void SRgbToLinear<TColor, TChannel>(Span<TColor> span)
private static float ToHalf(ReadOnlySpan<byte> input, int offset) where TChannel : unmanaged
where TColor : unmanaged, IColor<TChannel>
{ {
return (float)BinaryPrimitives.ReadHalfLittleEndian(input.Slice(offset, 2)); for (int i = 0; i < span.Length; i++)
{
span[i] = span[i].SRgbToLinear<TColor, TChannel>();
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void LinearToSRgb<TColor, TChannel>(Span<TColor> span)
private static float ToSingle(ReadOnlySpan<byte> input, int offset) where TChannel : unmanaged
where TColor : unmanaged, IColor<TChannel>
{ {
return BinaryPrimitives.ReadSingleLittleEndian(input.Slice(offset, 4)); for (int i = 0; i < span.Length; i++)
} {
span[i] = span[i].LinearToSRgb<TColor, TChannel>();
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] }
private static byte ClampByte(float x)
{
return byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static byte ClampByte(double x)
{
return byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue);
} }
} }
} }
+42
View File
@@ -0,0 +1,42 @@
namespace AssetRipper.TextureDecoder.Rgb;
internal static class SRgb
{
public static T ToLinear<T>(T value) where T : unmanaged
{
double d = NumericConversion.Convert<T, double>(value);
d = SRgbToLinear(d);
return NumericConversion.Convert<double, T>(d);
}
public static T FromLinear<T>(T value) where T : unmanaged
{
double d = NumericConversion.Convert<T, double>(value);
d = LinearToSRgb(d);
return NumericConversion.Convert<double, T>(d);
}
private static double SRgbToLinear(double value)
{
if (value <= 0.04045)
{
return value / 12.92;
}
else
{
return double.Pow((value + 0.055) / 1.055, 2.4);
}
}
private static double LinearToSRgb(double value)
{
if (value <= 0.0031308)
{
return value * 12.92;
}
else
{
return 1.055 * double.Pow(value, 1.0 / 2.4) - 0.055;
}
}
}
@@ -19,5 +19,13 @@
throw new InvalidOperationException($"Not enough space. {spaceRequired} is required, but only {spaceAvailable} is available"); 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;
using AssetRipper.TextureDecoder.Rgb.Formats;
namespace AssetRipper.TextureDecoder.Yuy2 namespace AssetRipper.TextureDecoder.Yuy2
{ {
public static class Yuy2Decoder 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> /// <summary>
/// Decompress a YUY2 image /// Decompress a YUY2 image
/// </summary> /// </summary>
@@ -63,6 +36,7 @@ namespace AssetRipper.TextureDecoder.Yuy2
where TOutputChannelValue : unmanaged where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue> where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{ {
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressYUY2<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output)); return DecompressYUY2<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
} }
+2 -3
View File
@@ -1,13 +1,12 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<LangVersion>Latest</LangVersion> <LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible> <IsAotCompatible>true</IsAotCompatible>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<Version>2.2.0</Version> <VersionPrefix>2.6.3</VersionPrefix>
<AssemblyVersion>$(Version)</AssemblyVersion>
<Copyright>Copyright © ds5678</Copyright> <Copyright>Copyright © ds5678</Copyright>
<Authors>ds5678</Authors> <Authors>ds5678</Authors>
<Company>AssetRipper</Company> <Company>AssetRipper</Company>
+74 -34
View File
@@ -8,17 +8,18 @@ internal class Program
{ {
int originalBitCount = int.Parse(args[0]); int originalBitCount = int.Parse(args[0]);
int newBitCount = int.Parse(args[1]); int newBitCount = int.Parse(args[1]);
EstimateConstants(originalBitCount, newBitCount, out uint a, out uint b, out int c); bool perfectMatch = EstimateConstants(originalBitCount, newBitCount, out uint a, out long b, out int c);
Console.WriteLine("Done!"); Console.WriteLine("Done!");
Console.WriteLine($"Result: ({a} * x + {b}) >> {c}"); Console.WriteLine($"Result: ({a} * x + {b}) >> {c}");
Console.WriteLine($"Perfect match: {perfectMatch}");
} }
private static void EstimateConstants(int originalBitCount, int newBitCount, out uint a, out uint b, out int c) private static bool EstimateConstants(int originalBitCount, int newBitCount, out uint a, out long b, out int c)
{ {
a = default; a = default;
b = default; b = default;
c = default; c = default;
float bestError = float.MaxValue;//Sum of squared errors double bestError = double.MaxValue;//Sum of squared errors
uint originalMaxValue = GetIntegerMaxValue(originalBitCount); uint originalMaxValue = GetIntegerMaxValue(originalBitCount);
uint newMaxValue = GetIntegerMaxValue(newBitCount); uint newMaxValue = GetIntegerMaxValue(newBitCount);
int bitDiff = newBitCount - originalBitCount; int bitDiff = newBitCount - originalBitCount;
@@ -27,55 +28,94 @@ internal class Program
//If bitDiff is negative, it means we are going from large numbers to small numbers. //If bitDiff is negative, it means we are going from large numbers to small numbers.
//In other words, we will always have to shift right by at least enough to overcome the bit surplus. //In other words, we will always have to shift right by at least enough to overcome the bit surplus.
int minc = int.Max(0, -bitDiff); int minc = int.Max(0, -bitDiff);
// We are limited to a 32-bit result, so we cannot shift left so much that we exceed 32 bits.
int maxc = 32 - newBitCount; int maxc = 32 - newBitCount;
for (int ic = minc; ic <= maxc; ic++) for (int ic = minc; ic <= maxc; ic++)
{ {
Console.WriteLine(ic); Console.WriteLine(ic);
//a: 2 ^ (c + bitDiff) to 2 ^ (c + bitDiff + 1) //a: 2^c * newMaxValue / originalMaxValue rounded to nearest integer
uint mina = Pow2(ic + bitDiff); double ia_exact = (double)Pow2(ic) * newMaxValue / originalMaxValue;
uint maxa = Pow2(ic + bitDiff + 1);
//b: 0 to 2^c - 1 ReadOnlySpan<uint> ia_values = ia_exact < 1 ? [(uint)double.Ceiling(ia_exact)] : [(uint)double.Floor(ia_exact), (uint)double.Ceiling(ia_exact)];
//Any bigger and it will cause 0 not to correspond with 0.
uint minb = 0;
uint maxb = Pow2(ic) - 1;
for (uint ia = mina; ia <= maxa; ia++) foreach (uint ia in ia_values)
{ {
if (ConvertEstimate(originalMaxValue, ia, minb, ic) > newMaxValue || ConvertEstimate(originalMaxValue, ia, maxb, ic) < newMaxValue) //b: 0 to 2^c - 1
//Any bigger and it will cause 0 not to correspond with 0.
uint minb;
uint maxb;
{ {
continue; //We can restrict b further by ensuring that originalMaxValue maps to newMaxValue.
long minb_one = (long)(newMaxValue << ic) - (ia * originalMaxValue);
long maxb_one = minb_one + Pow2(ic) - 1;
minb = (uint)long.Max(0, minb_one);
maxb = (uint)long.Min(Pow2(ic) - 1, long.Max(0, maxb_one));
minb = uint.Min(minb, maxb);
} }
for (uint ib = minb; ib <= maxb; ib++) //Errors for b are nearly parabolic, so we try to find the minimum by ternary search.
//This is not guaranteed to find the absolute minimum since there's noise in the error, but it should get close enough.
uint current_b_low = minb;
uint current_b_high = maxb;
while (current_b_high - current_b_low > 3)
{ {
if (ConvertEstimate(originalMaxValue, ia, ib, ic) == newMaxValue) uint range = current_b_high - current_b_low;
uint b1 = current_b_low + range / 3;
uint b2 = current_b_high - range / 3;
double error1 = CalculateMeanSquaredError(ia, b1, ic, originalMaxValue, newMaxValue, out _);
double error2 = CalculateMeanSquaredError(ia, b2, ic, originalMaxValue, newMaxValue, out _);
if (error1 < error2)
{ {
float error = 0; current_b_high = b2;
for (uint x = 1; x < originalMaxValue; x++) }
{ else
uint y = ConvertEstimate(x, ia, ib, ic); {
float yExact = (float)x * newMaxValue / originalMaxValue; current_b_low = b1;
float diff = yExact - y; }
error += diff * diff; }
}
if (error < bestError) for (uint ib = current_b_low; ib <= current_b_high; ib++)
{ {
a = ia; double error = CalculateMeanSquaredError(ia, ib, ic, originalMaxValue, newMaxValue, out bool anyIncorrect);
b = ib; if (error < bestError)
c = ic; {
if (error == 0)//Huge performance savings for when newBitCount / originalBitCount has no remainder. bestError = error;
{ a = ia;
return; b = ib;
} c = ic;
bestError = error; Console.WriteLine($"New best: a={a}, b={b}, c={c}, MSE={bestError}");
} }
if (!anyIncorrect)
{
//Perfect match
return true;
} }
} }
} }
} }
return false;
}
private static double CalculateMeanSquaredError(uint a, uint b, int c, uint originalMaxValue, uint newMaxValue, out bool anyIncorrect)
{
double meanSquaredError = 0;
anyIncorrect = false;
for (uint x = 0; x <= originalMaxValue; x++)
{
uint y = ConvertEstimate(x, a, b, c);
double yExact = (double)x * newMaxValue / originalMaxValue;
double diff = yExact - y;
meanSquaredError = meanSquaredError * x / (x + 1) + (diff * diff) / (x + 1);
uint yExactInt = (uint)double.Round(yExact, MidpointRounding.AwayFromZero);
anyIncorrect |= (y != yExactInt);
}
return meanSquaredError;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
-97
View File
@@ -1,97 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3AAF94E5-9A19-461C-923F-BBC603C630BB}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
Directory.Build.props = Directory.Build.props
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{226FED9B-5CD8-4FD0-8E77-660F542D7508}"
ProjectSection(SolutionItems) = preProject
.github\workflows\build.yml = .github\workflows\build.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Licenses", "Licenses", "{0BFF6817-43C8-43EE-8EE8-1A03B7ECEA1A}"
ProjectSection(SolutionItems) = preProject
Licenses\bcdec.md = Licenses\bcdec.md
Licenses\uTinyRipper.md = Licenses\uTinyRipper.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.TextureDecoder.Tests", "AssetRipper.TextureDecoder.Tests\AssetRipper.TextureDecoder.Tests.csproj", "{30E8E8F4-2CE4-4435-A49F-C5AF91D58FFE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.TextureDecoder", "AssetRipper.TextureDecoder\AssetRipper.TextureDecoder.csproj", "{E5A6E093-436C-4455-A597-3E357D99C4D9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.TextureDecoder.TestGenerator", "AssetRipper.TextureDecoder.TestGenerator\AssetRipper.TextureDecoder.TestGenerator.csproj", "{0074908C-35A8-4C71-8F74-A83A4095ACEA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.TextureDecoder.ConsoleApp", "AssetRipper.TextureDecoder.ConsoleApp\AssetRipper.TextureDecoder.ConsoleApp.csproj", "{FFC8EEF8-8BF1-4B65-ADDA-A949EDDF4CB4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.TextureDecoder.Benchmarks", "AssetRipper.TextureDecoder.Benchmarks\AssetRipper.TextureDecoder.Benchmarks.csproj", "{F85804F4-4864-412E-BDC6-8B6E2B5646A7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.TextureDecoder.ColorGenerator", "AssetRipper.TextureDecoder.ColorGenerator\AssetRipper.TextureDecoder.ColorGenerator.csproj", "{E9ED1120-1CEA-433E-9BFF-C4B20A34CC7B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegerConversion.ConstantEstimation", "IntegerConversion.ConstantEstimation\IntegerConversion.ConstantEstimation.csproj", "{9897C52D-2F38-405A-AB85-B06383FDF6E0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.TextureDecoder.SourceGeneration.Common", "AssetRipper.TextureDecoder.SourceGeneration.Common\AssetRipper.TextureDecoder.SourceGeneration.Common.csproj", "{336CF443-373C-41B3-8E7C-881672E9875E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetRipper.TextureDecoder.Paths", "AssetRipper.TextureDecoder.Paths\AssetRipper.TextureDecoder.Paths.csproj", "{48731523-A672-4D39-A49E-2C4C253B4491}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{30E8E8F4-2CE4-4435-A49F-C5AF91D58FFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30E8E8F4-2CE4-4435-A49F-C5AF91D58FFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30E8E8F4-2CE4-4435-A49F-C5AF91D58FFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30E8E8F4-2CE4-4435-A49F-C5AF91D58FFE}.Release|Any CPU.Build.0 = Release|Any CPU
{E5A6E093-436C-4455-A597-3E357D99C4D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5A6E093-436C-4455-A597-3E357D99C4D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5A6E093-436C-4455-A597-3E357D99C4D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5A6E093-436C-4455-A597-3E357D99C4D9}.Release|Any CPU.Build.0 = Release|Any CPU
{0074908C-35A8-4C71-8F74-A83A4095ACEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0074908C-35A8-4C71-8F74-A83A4095ACEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0074908C-35A8-4C71-8F74-A83A4095ACEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0074908C-35A8-4C71-8F74-A83A4095ACEA}.Release|Any CPU.Build.0 = Release|Any CPU
{FFC8EEF8-8BF1-4B65-ADDA-A949EDDF4CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFC8EEF8-8BF1-4B65-ADDA-A949EDDF4CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFC8EEF8-8BF1-4B65-ADDA-A949EDDF4CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFC8EEF8-8BF1-4B65-ADDA-A949EDDF4CB4}.Release|Any CPU.Build.0 = Release|Any CPU
{F85804F4-4864-412E-BDC6-8B6E2B5646A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F85804F4-4864-412E-BDC6-8B6E2B5646A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F85804F4-4864-412E-BDC6-8B6E2B5646A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F85804F4-4864-412E-BDC6-8B6E2B5646A7}.Release|Any CPU.Build.0 = Release|Any CPU
{E9ED1120-1CEA-433E-9BFF-C4B20A34CC7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9ED1120-1CEA-433E-9BFF-C4B20A34CC7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9ED1120-1CEA-433E-9BFF-C4B20A34CC7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9ED1120-1CEA-433E-9BFF-C4B20A34CC7B}.Release|Any CPU.Build.0 = Release|Any CPU
{9897C52D-2F38-405A-AB85-B06383FDF6E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9897C52D-2F38-405A-AB85-B06383FDF6E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9897C52D-2F38-405A-AB85-B06383FDF6E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9897C52D-2F38-405A-AB85-B06383FDF6E0}.Release|Any CPU.Build.0 = Release|Any CPU
{336CF443-373C-41B3-8E7C-881672E9875E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{336CF443-373C-41B3-8E7C-881672E9875E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{336CF443-373C-41B3-8E7C-881672E9875E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{336CF443-373C-41B3-8E7C-881672E9875E}.Release|Any CPU.Build.0 = Release|Any CPU
{48731523-A672-4D39-A49E-2C4C253B4491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48731523-A672-4D39-A49E-2C4C253B4491}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48731523-A672-4D39-A49E-2C4C253B4491}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48731523-A672-4D39-A49E-2C4C253B4491}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{226FED9B-5CD8-4FD0-8E77-660F542D7508} = {3AAF94E5-9A19-461C-923F-BBC603C630BB}
{0BFF6817-43C8-43EE-8EE8-1A03B7ECEA1A} = {3AAF94E5-9A19-461C-923F-BBC603C630BB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4E786DF-F6DD-407C-B23E-854CF00B7801}
EndGlobalSection
EndGlobal