Compare commits

...
29 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
29 changed files with 1072 additions and 982 deletions

No files matched your search

+2 -2
View File
@@ -12,9 +12,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v7
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v5.0.0 uses: actions/setup-dotnet@v6.0.0
with: with:
dotnet-version: 9.0.x dotnet-version: 9.0.x
- name: Restore dependencies - name: Restore dependencies
@@ -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<ColorARGB<byte>, byte, ColorBGRA<byte>, 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.15.4" />
</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, ColorBGRA<byte>, byte>(inputData, Width, Height, outputData);
}
@@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" /> <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");
@@ -13,18 +13,23 @@ 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 ),
( "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 ),
}; ];
private static readonly List<string> GenericColors = private static readonly string[] GenericColors =
[ [
"ColorR", "ColorR",
"ColorRG", "ColorRG",
@@ -38,7 +43,7 @@ internal static partial class Program
static void Main() static void Main()
{ {
foreach (var customColor in CustomColors) foreach (CustomColorDetails customColor in CustomColors)
{ {
WriteCustomColor(customColor); WriteCustomColor(customColor);
} }
@@ -54,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);
@@ -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.2.2" /> <PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -5,17 +5,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="NUnit" Version="4.4.0" /> <PackageReference Include="NUnit" Version="4.6.1" />
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" /> <PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.10.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.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -77,6 +77,8 @@ 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<ColorBGRA<byte>, byte>(block, blockWidth, blockHeight, blockWidth, blockHeight, out _)); Assert.DoesNotThrow(() => AstcDecoder.DecodeASTC<ColorBGRA<byte>, byte>(block, blockWidth, blockHeight, blockWidth, blockHeight, out _));
@@ -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);
}
}
@@ -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,7 +12,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.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, ColorBGRA<byte>, 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));
@@ -40,7 +40,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.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, ColorBGRA<byte>, 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,7 +55,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.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, ColorBGRA<byte>, 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,7 +70,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.ARGB32ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData); int bytesRead = OldRgbConverter.ARGB32ToBGRA32(data.Slice(totalBytesRead), size, size, out byte[] originalDecodedData);
RgbConverter.Convert<ColorARGB<byte>, byte, ColorBGRA<byte>, 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,7 +85,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.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, ColorBGRA<byte>, 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));
@@ -126,7 +126,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.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, ColorBGRA<byte>, 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,7 +141,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.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, ColorBGRA<byte>, 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,7 +153,7 @@ 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, ColorBGRA<byte>, 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,7 +162,7 @@ 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, ColorBGRA<byte>, 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,7 +171,7 @@ 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, ColorBGRA<byte>, 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,7 +180,7 @@ 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, ColorBGRA<byte>, 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,7 +189,7 @@ 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, ColorBGRA<byte>, 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,7 +198,7 @@ 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, ColorBGRA<byte>, 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,7 +207,7 @@ 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, ColorBGRA<byte>, 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,7 +216,7 @@ 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, ColorBGRA<byte>, 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,7 +225,7 @@ 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, ColorBGRA<byte>, 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,7 +234,7 @@ 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, ColorBGRA<byte>, 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,7 +13,6 @@
<Folder Name="/Solution Items/Workflows/"> <Folder Name="/Solution Items/Workflows/">
<File Path=".github/workflows/build.yml" /> <File Path=".github/workflows/build.yml" />
</Folder> </Folder>
<Project Path="AssetRipper.TextureDecoder.Benchmarks/AssetRipper.TextureDecoder.Benchmarks.csproj" />
<Project Path="AssetRipper.TextureDecoder.ColorGenerator/AssetRipper.TextureDecoder.ColorGenerator.csproj" /> <Project Path="AssetRipper.TextureDecoder.ColorGenerator/AssetRipper.TextureDecoder.ColorGenerator.csproj" />
<Project Path="AssetRipper.TextureDecoder.ConsoleApp/AssetRipper.TextureDecoder.ConsoleApp.csproj" /> <Project Path="AssetRipper.TextureDecoder.ConsoleApp/AssetRipper.TextureDecoder.ConsoleApp.csproj" />
<Project Path="AssetRipper.TextureDecoder.Paths/AssetRipper.TextureDecoder.Paths.csproj" /> <Project Path="AssetRipper.TextureDecoder.Paths/AssetRipper.TextureDecoder.Paths.csproj" />
@@ -1,26 +1,25 @@
<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>
<PackageTags>C# Texture</PackageTags> <PackageTags>C# Texture</PackageTags>
<RepositoryUrl>https://github.com/AssetRipper/TextureDecoder</RepositoryUrl> <RepositoryUrl>https://github.com/AssetRipper/TextureDecoder</RepositoryUrl>
<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>
@@ -18,7 +18,7 @@ namespace AssetRipper.TextureDecoder.Astc
where TOutputChannelValue : unmanaged where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue> where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{ {
output = new byte[width * height * 4]; output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return DecodeASTC<TOutputColor, TOutputChannelValue>(input, width, height, blockWidth, blockHeight, output); return DecodeASTC<TOutputColor, TOutputChannelValue>(input, width, height, blockWidth, blockHeight, output);
} }
@@ -66,10 +66,7 @@ namespace AssetRipper.TextureDecoder.Astc
if (input[0] == 0xfc && (input[1] & 1) == 1) if (input[0] == 0xfc && (input[1] & 1) == 1)
{ {
ColorRGBA<byte> c = new(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
{ {
@@ -886,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))
@@ -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
+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;
}
} }
@@ -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
+14 -501
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
@@ -548,28 +65,24 @@ namespace AssetRipper.TextureDecoder.Rgb
} }
} }
[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;
}
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.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>
<VersionPrefix>2.5.0</VersionPrefix> <VersionPrefix>2.6.3</VersionPrefix>
<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)]