Compare commits

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-06 19:33:32 -07:00
ds5678 19b2469595 Bump version 2024-08-03 10:25:22 -07:00
ds5678 c5a2382733 Fix bc7 mode reading regression 2024-08-02 20:56:59 -07:00
ds5678 9ce8311e1b Bc optimizations 2024-08-01 23:33:37 -07:00
ds5678 e39b439ccd Use UInt128 in BitStream 2024-08-01 23:09:20 -07:00
ds5678 f2da43bcbb BitStream optimizations 2024-08-01 22:43:08 -07:00
ds5678 128eb58864 Use unsigned integers for Bc decoding 2024-08-01 22:34:02 -07:00
dependabot[bot] 9f7a61172a Bump NUnit3TestAdapter from 4.5.0 to 4.6.0
Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases)
- [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V4.5.0...V4.6.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2024-07-25 21:26:33 -07:00
dependabot[bot] 93c177232f Bump NUnit.Analyzers from 4.1.0 to 4.2.0
Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/nunit/nunit.analyzers/releases)
- [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit.analyzers/compare/4.1.0...4.2.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2024-07-09 21:24:01 -07:00
dependabot[bot] 8b62957cdb Bump Microsoft.NET.Test.Sdk from 17.9.0 to 17.10.0
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.9.0 to 17.10.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.9.0...v17.10.0)

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

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

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

Signed-off-by: dependabot[bot] <[email protected]>
2024-07-09 21:23:32 -07:00
ds5678 f8d17ad290 Use ReadOnlySpan for constant data 2024-04-14 20:24:19 -07:00
Jeremy Pritts f2772dcb83 Bump version 2024-04-01 21:33:33 -07:00
Jeremy Pritts 1e363e355e Update NuGet references 2024-03-24 17:03:38 -07:00
Jeremy Pritts 8e82e702a7 OpenEXR writing 2024-03-24 17:00:42 -07:00
Jeremy Pritts 1957784de4 Support specifying channels 2024-03-24 16:59:44 -07:00
Jeremy Pritts 1bf5f70ead Rename Channel to ChannelValue 2024-03-24 16:54:23 -07:00
dependabot[bot] 2bb946b26f Bump BenchmarkDotNet from 0.13.11 to 0.13.12
Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.11 to 0.13.12.
- [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases)
- [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.11...v0.13.12)

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

Signed-off-by: dependabot[bot] <[email protected]>
2024-01-07 21:18:06 -05:00
ds5678 eed528cc68 Bump version 2023-12-30 17:01:05 -05:00
ds5678 c6d3236770 Resolve #59 2023-12-30 16:42:13 -05:00
Jeremy Pritts 34d8310764 Add test for ASTC error 2023-12-14 00:18:06 -05:00
70 changed files with 2411 additions and 394 deletions

No files matched your search

+2 -2
View File
@@ -14,9 +14,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4.0.0
uses: actions/setup-dotnet@v4.3.1
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.11" />
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
</ItemGroup>
<ItemGroup>
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" />
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" />
</ItemGroup>
<ItemGroup>
@@ -7,9 +7,10 @@ namespace AssetRipper.TextureDecoder.ColorGenerator;
internal static partial class Program
{
private const string AttributeNamespace = "AssetRipper.TextureDecoder.Attributes";
private const string OutputNamespace = "AssetRipper.TextureDecoder.Rgb.Formats";
private const string OutputFolder = "../../../../AssetRipper.TextureDecoder/Rgb/Formats/";
private const string RgbNamespace = "AssetRipper.TextureDecoder.Rgb";
private const string RgbFolder = "../../../../AssetRipper.TextureDecoder/Rgb/";
private const string FormatsNamespace = "AssetRipper.TextureDecoder.Rgb.Formats";
private const string FormatsFolder = "../../../../AssetRipper.TextureDecoder/Rgb/Formats/";
/// <summary>
/// Name, Type, Red, Blue, Green, Alpha, Fully Utilized
@@ -46,6 +47,10 @@ internal static partial class Program
{
WriteGenericColor(genericColor);
}
for (int i = 1; i <= 4; i++)
{
WriteSuperGenericColor(i);
}
NumericConversionGenerator.Run();
Console.WriteLine("Done!");
}
@@ -54,7 +59,7 @@ internal static partial class Program
{
(string name, Type type, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, bool fullyUtilized) = details;
Console.WriteLine(name);
using IndentedTextWriter writer = IndentedTextWriterFactory.Create(OutputFolder, name);
using IndentedTextWriter writer = IndentedTextWriterFactory.Create(FormatsFolder, name);
WriteCustomColor(writer, name, type, hasRed, hasGreen, hasBlue, hasAlpha, fullyUtilized);
}
@@ -62,16 +67,24 @@ internal static partial class Program
{
(string name, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha) = details;
Console.WriteLine(name);
using IndentedTextWriter writer = IndentedTextWriterFactory.Create(OutputFolder, name);
using IndentedTextWriter writer = IndentedTextWriterFactory.Create(FormatsFolder, name);
WriteGenericColor(writer, name, hasRed, hasGreen, hasBlue, hasAlpha);
}
private static void WriteSuperGenericColor(int channelCount)
{
string name = $"Color`{channelCount}";
Console.WriteLine(name);
using IndentedTextWriter writer = IndentedTextWriterFactory.Create(RgbFolder, name);
WriteSuperGenericColor(writer, channelCount);
}
private static void WriteCustomColor(IndentedTextWriter writer, string name, Type type, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, bool fullyUtilized)
{
string typeName = CSharpPrimitives.Dictionary[type].LangName;
writer.WriteGeneratedCodeWarning();
writer.WriteLine();
using (new Namespace(writer, OutputNamespace))
using (new Namespace(writer, FormatsNamespace))
{
writer.WriteLine($"public partial struct {name} : IColor<{typeName}>");
using (new CurlyBrackets(writer))
@@ -129,11 +142,16 @@ internal static partial class Program
private static void WriteNonGenericStaticProperties(IndentedTextWriter writer, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, bool fullyUtilized, string typeName)
{
writer.WriteLine($"static bool IColor.HasRedChannel => {hasRed.ToLowerString()};");
writer.WriteLine($"static bool IColor.HasGreenChannel => {hasGreen.ToLowerString()};");
writer.WriteLine($"static bool IColor.HasBlueChannel => {hasBlue.ToLowerString()};");
writer.WriteLine($"static bool IColor.HasAlphaChannel => {hasAlpha.ToLowerString()};");
writer.WriteLine($"static bool IColor.ChannelsAreFullyUtilized => {fullyUtilized.ToLowerString()};");
WriteNonGenericStaticProperties(writer, hasRed.ToLowerString(), hasGreen.ToLowerString(), hasBlue.ToLowerString(), hasAlpha.ToLowerString(), fullyUtilized.ToLowerString(), typeName);
}
private static void WriteNonGenericStaticProperties(IndentedTextWriter writer, string hasRed, string hasGreen, string hasBlue, string hasAlpha, string fullyUtilized, string typeName)
{
writer.WriteLine($"static bool IColor.HasRedChannel => {hasRed};");
writer.WriteLine($"static bool IColor.HasGreenChannel => {hasGreen};");
writer.WriteLine($"static bool IColor.HasBlueChannel => {hasBlue};");
writer.WriteLine($"static bool IColor.HasAlphaChannel => {hasAlpha};");
writer.WriteLine($"static bool IColor.ChannelsAreFullyUtilized => {fullyUtilized};");
writer.WriteLine($"static Type IColor.ChannelType => typeof({typeName});");
}
@@ -205,7 +223,7 @@ internal static partial class Program
writer.WriteGeneratedCodeWarning();
writer.WriteLineNoTabs();
writer.WriteFileScopedNamespace(OutputNamespace);
writer.WriteFileScopedNamespace(FormatsNamespace);
writer.WriteLineNoTabs();
writer.WriteLine($"public partial struct {name}<T> : IColor<{name}<T>, T> where T : unmanaged, INumberBase<T>, IMinMaxValue<T>");
@@ -234,6 +252,165 @@ internal static partial class Program
}
}
private static void WriteSuperGenericColor(IndentedTextWriter writer, int channelCount)
{
const string typeName = "TChannelValue";
const string minValue = $"NumericConversion.GetMinimumValueSafe<{typeName}>()";
const string maxValue = $"NumericConversion.GetMaximumValueSafe<{typeName}>()";
string[] channelTypeParameters = channelCount switch
{
1 => ["TChannel"],
2 => ["TChannel1", "TChannel2"],
3 => ["TChannel1", "TChannel2", "TChannel3"],
4 => ["TChannel1", "TChannel2", "TChannel3", "TChannel4"],
_ => throw new(),
};
string[] fieldNames = channelCount switch
{
1 => ["value"],
2 => ["value1", "value2"],
3 => ["value1", "value2", "value3"],
4 => ["value1", "value2", "value3", "value4"],
_ => throw new(),
};
writer.WriteGeneratedCodeWarning();
writer.WriteLineNoTabs();
writer.WriteUsing("AssetRipper.TextureDecoder.Rgb.Channels");
writer.WriteLineNoTabs();
writer.WriteFileScopedNamespace("AssetRipper.TextureDecoder.Rgb");
writer.WriteLineNoTabs();
string structName;
{
StringBuilder sb = new();
sb.Append("Color<");
sb.Append(typeName);
for (int i = 0; i < channelTypeParameters.Length; i++)
{
sb.Append(", ");
sb.Append(channelTypeParameters[i]);
}
sb.Append('>');
structName = sb.ToString();
}
writer.WriteLine($"public partial struct {structName} : IColor<{structName}, {typeName}>");
using (new Indented(writer))
{
writer.WriteLine($"where {typeName} : unmanaged, INumberBase<{typeName}>, IMinMaxValue<{typeName}>");
foreach (string channel in channelTypeParameters)
{
writer.WriteLine($"where {channel} : IChannel");
}
}
using (new CurlyBrackets(writer))
{
foreach (string fieldName in fieldNames)
{
writer.WriteLine($"private {typeName} {fieldName};");
}
writer.WriteLineNoTabs();
WriteProperty(writer, 'R', "Red", channelTypeParameters, fieldNames, minValue, typeName);
writer.WriteLineNoTabs();
WriteProperty(writer, 'G', "Green", channelTypeParameters, fieldNames, minValue, typeName);
writer.WriteLineNoTabs();
WriteProperty(writer, 'B', "Blue", channelTypeParameters, fieldNames, minValue, typeName);
writer.WriteLineNoTabs();
WriteProperty(writer, 'A', "Alpha", channelTypeParameters, fieldNames, maxValue, typeName);
writer.WriteLineNoTabs();
writer.Write("public Color(");
for (int i = 0; i < fieldNames.Length; i++)
{
if (i != 0)
{
writer.Write(", ");
}
writer.Write(typeName);
writer.Write(' ');
writer.Write(fieldNames[i]);
}
writer.WriteLine(')');
using (new CurlyBrackets(writer))
{
foreach (string fieldName in fieldNames)
{
writer.WriteLine($"this.{fieldName} = {fieldName};");
}
}
writer.WriteLineNoTabs();
WriteGetChannels(writer, typeName);
writer.WriteLineNoTabs();
WriteSetChannels(writer, typeName, true, true, true, true);
writer.WriteLineNoTabs();
WriteNonGenericStaticProperties(
writer,
string.Join(" || ", channelTypeParameters.Select(c => $"{c}.IsRed")),
string.Join(" || ", channelTypeParameters.Select(c => $"{c}.IsGreen")),
string.Join(" || ", channelTypeParameters.Select(c => $"{c}.IsBlue")),
string.Join(" || ", channelTypeParameters.Select(c => $"{c}.IsAlpha")),
string.Join(" && ", channelTypeParameters.Select(c => $"{c}.FullyUtilized")),
typeName);
writer.WriteLineNoTabs();
writer.WriteLine($"public static {structName} Black");
using (new CurlyBrackets(writer))
{
writer.WriteLine($"get => new({string.Join(", ", channelTypeParameters.Select(c => $"{c}.GetBlack<{typeName}>()"))});");
}
writer.WriteLineNoTabs();
writer.WriteLine($"public static {structName} White");
using (new CurlyBrackets(writer))
{
writer.WriteLine($"get => new({string.Join(", ", channelTypeParameters.Select(c => $"{c}.GetWhite<{typeName}>()"))});");
}
writer.WriteLineNoTabs();
WriteToString(writer, true, true, true, true);
}
static void WriteProperty(IndentedTextWriter writer, char property, string channel, string[] channels, string[] fields, string defaultValue, string typeName)
{
writer.WriteLine($"public {typeName} {property}");
using (new CurlyBrackets(writer))
{
writer.WriteLine("readonly get");
using (new CurlyBrackets(writer))
{
If.Write(
writer,
Enumerable.Range(0, channels.Length).Select(i => new ValueTuple<string, Action<IndentedTextWriter>?>($"{channels[i]}.Is{channel}", (writer) =>
{
writer.WriteLine($"return {channels[i]}.Get{channel}({fields[i]});");
})),
(writer) =>
{
writer.WriteLine($"return {defaultValue};");
});
}
writer.WriteLine("set");
using (new CurlyBrackets(writer))
{
for (int i = 0; i < channels.Length; i++)
{
writer.WriteLine($"Channel.SetIf{channel}<{channels[i]}, {typeName}>(ref {fields[i]}, value);");
}
}
}
}
}
private static void WriteConstructor(IndentedTextWriter writer, string declaringTypeName, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, string channelTypeName)
{
writer.Write($"public {declaringTypeName}(");
@@ -1,4 +1,6 @@
using AssetRipper.TextureDecoder.Exr;
using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Channels;
using AssetRipper.TextureDecoder.Rgb.Formats;
using StbImageWriteSharp;
using System.Runtime.CompilerServices;
@@ -54,6 +56,12 @@ public readonly struct DirectBitmap<TColor, TColorArg>
}
}
public void SaveAsExr(string path)
{
using Stream stream = File.OpenWrite(path);
ExrWriter.Write<TColor, TColorArg>(stream, Width, Height, Pixels);
}
public void SaveAsHdr(string path)
{
GetDataAndComponentsForSaving(out byte[] data, out ColorComponents components);
@@ -96,24 +104,34 @@ public readonly struct DirectBitmap<TColor, TColorArg>
private void GetDataAndComponentsForSaving(out byte[] data, out ColorComponents components)
{
if (typeof(TColor) == typeof(ColorRGBA<byte>))
if (typeof(TColor) == typeof(Color<byte, R, G, B, A>) || typeof(TColor) == typeof(ColorRGBA<byte>))
{
data = Data;
components = ColorComponents.RedGreenBlueAlpha;
}
else if (typeof(TColor) == typeof(ColorRGB<byte>))
else if (typeof(TColor) == typeof(Color<byte, R, G, B>) || typeof(TColor) == typeof(ColorRGB<byte>))
{
data = Data;
components = ColorComponents.RedGreenBlue;
}
else if (typeof(TColor) == typeof(Color<byte, Gray, A>))
{
data = Data;
components = ColorComponents.GreyAlpha;
}
else if (typeof(TColor) == typeof(Color<byte, Gray>))
{
data = Data;
components = ColorComponents.Grey;
}
else if (TColor.HasAlphaChannel)
{
RgbConverter.Convert<TColor, TColorArg, ColorRGBA<byte>, byte>(Bits, Width, Height, out data);
RgbConverter.Convert<TColor, TColorArg, Color<byte, R, G, B, A>, byte>(Bits, Width, Height, out data);
components = ColorComponents.RedGreenBlueAlpha;
}
else
{
RgbConverter.Convert<TColor, TColorArg, ColorRGB<byte>, byte>(Bits, Width, Height, out data);
RgbConverter.Convert<TColor, TColorArg, Color<byte, R, G, B>, byte>(Bits, Width, Height, out data);
components = ColorComponents.RedGreenBlue;
}
}
@@ -63,18 +63,32 @@ internal static partial class Program
switch (outputType)
{
case "png":
{
string newPath = Path.Combine(dirPath, name + ".png");
bitmap.FlipY();
bitmap.SaveAsPng(newPath);
}
break;
case "bgra":
{
string newPath = Path.Combine(dirPath, name + ".bgra");
WriteAllBytes(newPath, bitmap.Bits);
}
WriteAllBytes(Path.Combine(dirPath, name + ".bgra"), bitmap.Bits);
break;
case "bmp":
bitmap.FlipY();
bitmap.SaveAsBmp(Path.Combine(dirPath, name + ".bmp"));
break;
case "exr":
bitmap.FlipY();
bitmap.SaveAsExr(Path.Combine(dirPath, name + ".exr"));
break;
case "hdr":
bitmap.FlipY();
bitmap.SaveAsHdr(Path.Combine(dirPath, name + ".hdr"));
break;
case "jpg":
bitmap.FlipY();
bitmap.SaveAsJpg(Path.Combine(dirPath, name + ".jpg"));
break;
case "png":
bitmap.FlipY();
bitmap.SaveAsPng(Path.Combine(dirPath, name + ".png"));
break;
case "tga":
bitmap.FlipY();
bitmap.SaveAsTga(Path.Combine(dirPath, name + ".tga"));
break;
default:
throw new NotSupportedException(outputType);
@@ -239,4 +253,4 @@ internal static partial class Program
using Microsoft.Win32.SafeHandles.SafeFileHandle sfh = File.OpenHandle(path, FileMode.Create, FileAccess.Write, FileShare.Read);
RandomAccess.Write(sfh, bytes, 0);
}
}
}
@@ -11,7 +11,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" />
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" />
</ItemGroup>
</Project>
@@ -62,7 +62,7 @@ namespace AssetRipper.TextureDecoder.TestGenerator
writer.WriteLine($"case {i}:");
using (new Indented(writer))
{
writer.WriteLine($"RgbConverter.Convert<{colorName}, {channelName}, ColorBGRA32, byte>(input, width, height, output);");
writer.WriteLine($"RgbConverter.Convert<{colorName}, {channelName}, Color<byte, B, G, R, A>, byte>(input, width, height, output);");
writer.WriteLine("break;");
}
}
@@ -127,7 +127,7 @@ internal static class TestClassGenerator
writer.WriteLine($"[TestFixture(TypeArgs = new Type[] {{ typeof({colorName}<{channeName}>), typeof({channeName}) }})]");
}
}
writer.WriteLine("partial class GenericColorTests<TColor, TChannel>");
writer.WriteLine("partial class GenericColorTests<TColor, TChannelValue>");
using (new CurlyBrackets(writer))
{
}
@@ -354,4 +354,4 @@ internal static class TestClassGenerator
textWriter.WriteLine();
}
}
}
}
@@ -5,14 +5,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.10.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -74,4 +74,10 @@ public sealed class AstcTests
}
ByteArrayDeviation.AssertMinimalDeviation(decompressedData, AndroidTextures.Logo_00.Data, maxMeanDeviation, maxStandardDeviation);
}
[TestCase(new byte[16] { 83, 1, 147, 104, 198, 173, 55, 116, 182, 66, 105, 11, 102, 43, 148, 125 }, 8, 8, TestName = "GetBits Should not throw in DecodeIntseq")]
public void DecodesBlockWithoutThrowing(byte[] block, int blockWidth, int blockHeight)
{
Assert.DoesNotThrow(() => AstcDecoder.DecodeASTC(block, blockWidth, blockHeight, blockWidth, blockHeight, out _));
}
}
+80 -9
View File
@@ -21,7 +21,7 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressDXT1Test()
public void Decompress_BC1()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1");
int totalBytesRead = 0;
@@ -36,7 +36,7 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressDXT3Test()
public void Decompress_BC2()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3");
int totalBytesRead = 0;
@@ -51,7 +51,7 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressDXT5Test()
public void Decompress_BC3()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5");
int totalBytesRead = 0;
@@ -66,7 +66,7 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressBC4Test()
public void Decompress_BC4()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4");
int bytesRead = Bc4.Decompress(data, 512, 512, out _);
@@ -74,7 +74,7 @@ namespace AssetRipper.TextureDecoder.Tests
}
[Test]
public void DecompressBC5Test()
public void Decompress_BC5()
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5");
int bytesRead = Bc5.Decompress(data, 512, 512, out _);
@@ -84,7 +84,7 @@ namespace AssetRipper.TextureDecoder.Tests
[TestCase("test.bc6h_fast")]
[TestCase("test.bc6h_normal")]
[TestCase("test.bc6h_best")]
public void DecompressBC6HTest(string fileName)
public void Decompress_BC6H(string fileName)
{
AssertCorrectBC6HDecompression(TestFileFolders.BcTestFiles + fileName, 512, 512, false);
}
@@ -92,14 +92,85 @@ namespace AssetRipper.TextureDecoder.Tests
[TestCase("test.bc7_fast")]
[TestCase("test.bc7_normal")]
[TestCase("test.bc7_best")]
public void DecompressBC7Test(string fileName)
public void Decompress_BC7(string fileName)
{
AssertCorrectBC7Decompression(TestFileFolders.BcTestFiles + fileName, 512, 512);
}
[Test]
[Ignore("Not implemented")]
public void BC7PartialBlockTest([Range(1, 4)] int width, [Range(1, 4)] int height)
public void PartialBlock_BC1([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1").AsSpan()[..Bc1.BlockSize];
int bytesRead = Bc1.Decompress(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC2([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3").AsSpan()[..Bc2.BlockSize];
int bytesRead = Bc2.Decompress(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC3([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5").AsSpan()[..Bc3.BlockSize];
int bytesRead = Bc3.Decompress(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC4([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4").AsSpan()[..Bc4.BlockSize];
int bytesRead = Bc4.Decompress(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC5([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5").AsSpan()[..Bc5.BlockSize];
int bytesRead = Bc5.Decompress(data, width, height, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC6H([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc6h_best").AsSpan()[..Bc6h.BlockSize];
int bytesRead = Bc6h.Decompress(data, width, height, false, out byte[] decodedData);
Assert.That(bytesRead, Is.EqualTo(data.Length));
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
});
}
[Test]
public void PartialBlock_BC7([Range(1, 4)] int width, [Range(1, 4)] int height)
{
Assert.Multiple(() =>
{
@@ -1,12 +1,12 @@
using AssetRipper.TextureDecoder.Rgb;
namespace AssetRipper.TextureDecoder.Tests.Formats;
internal static class ColorRandom<TColor, TChannel> where TColor : unmanaged, IColor<TChannel> where TChannel : unmanaged
internal static class ColorRandom<TColor, TChannelValue> where TColor : unmanaged, IColor<TChannelValue> where TChannelValue : unmanaged
{
public static TChannel MakeRandomValue()
public static TChannelValue MakeRandomValue()
{
ulong value = TestContext.CurrentContext.Random.NextULong();
return NumericConversion.Convert<ulong, TChannel>(value);
return NumericConversion.Convert<ulong, TChannelValue>(value);
}
public static TColor MakeRandomColor()
@@ -3,19 +3,19 @@ using System.Runtime.CompilerServices;
namespace AssetRipper.TextureDecoder.Tests.Formats.Generic;
internal partial class GenericColorTests<TColor, TChannel> where TColor : unmanaged, IColor<TChannel> where TChannel : unmanaged
internal partial class GenericColorTests<TColor, TChannelValue> where TColor : unmanaged, IColor<TChannelValue> where TChannelValue : unmanaged
{
[Test]
public void CorrectSizeTest()
{
Assert.That(Unsafe.SizeOf<TColor>(), Is.EqualTo(Color.GetChannelCount<TColor>() * Unsafe.SizeOf<TChannel>()));
Assert.That(Unsafe.SizeOf<TColor>(), Is.EqualTo(Color.GetChannelCount<TColor>() * Unsafe.SizeOf<TChannelValue>()));
}
[Test]
public void PropertyIsSymmetric_R()
{
TColor color = MakeRandomColor();
TChannel r = color.R;
TChannelValue r = color.R;
color.R = r;
Assert.That(color.R, Is.EqualTo(r));
}
@@ -24,7 +24,7 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void PropertyIsSymmetric_G()
{
TColor color = MakeRandomColor();
TChannel g = color.G;
TChannelValue g = color.G;
color.G = g;
Assert.That(color.G, Is.EqualTo(g));
}
@@ -33,7 +33,7 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void PropertyIsSymmetric_B()
{
TColor color = MakeRandomColor();
TChannel b = color.B;
TChannelValue b = color.B;
color.B = b;
Assert.That(color.B, Is.EqualTo(b));
}
@@ -42,7 +42,7 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void PropertyIsSymmetric_A()
{
TColor color = MakeRandomColor();
TChannel a = color.A;
TChannelValue a = color.A;
color.A = a;
Assert.That(color.A, Is.EqualTo(a));
}
@@ -51,9 +51,9 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void ChannelsAreIndependent_R()
{
TColor color = MakeRandomColor();
TChannel g = color.G;
TChannel b = color.B;
TChannel a = color.A;
TChannelValue g = color.G;
TChannelValue b = color.B;
TChannelValue a = color.A;
color.R = MakeRandomValue();
Assert.Multiple(() =>
{
@@ -67,9 +67,9 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void ChannelsAreIndependent_G()
{
TColor color = MakeRandomColor();
TChannel r = color.R;
TChannel b = color.B;
TChannel a = color.A;
TChannelValue r = color.R;
TChannelValue b = color.B;
TChannelValue a = color.A;
color.G = MakeRandomValue();
Assert.Multiple(() =>
{
@@ -83,9 +83,9 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void ChannelsAreIndependent_B()
{
TColor color = MakeRandomColor();
TChannel r = color.R;
TChannel g = color.G;
TChannel a = color.A;
TChannelValue r = color.R;
TChannelValue g = color.G;
TChannelValue a = color.A;
color.B = MakeRandomValue();
Assert.Multiple(() =>
{
@@ -99,9 +99,9 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void ChannelsAreIndependent_A()
{
TColor color = MakeRandomColor();
TChannel r = color.R;
TChannel g = color.G;
TChannel b = color.B;
TChannelValue r = color.R;
TChannelValue g = color.G;
TChannelValue b = color.B;
color.A = MakeRandomValue();
Assert.Multiple(() =>
{
@@ -115,7 +115,7 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void GetMethodMatchesProperties()
{
TColor color = MakeRandomColor();
color.GetChannels(out TChannel r, out TChannel g, out TChannel b, out TChannel a);
color.GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a);
Assert.Multiple(() =>
{
Assert.That(color.R, Is.EqualTo(r));
@@ -129,7 +129,7 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
public void MethodsAreSymmetric()
{
TColor color = MakeRandomColor();
color.GetChannels(out TChannel r, out TChannel g, out TChannel b, out TChannel a);
color.GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a);
color.SetChannels(r, g, b, a);
Assert.Multiple(() =>
{
@@ -140,7 +140,7 @@ internal partial class GenericColorTests<TColor, TChannel> where TColor : unmana
});
}
private static TChannel MakeRandomValue() => ColorRandom<TColor, TChannel>.MakeRandomValue();
private static TChannelValue MakeRandomValue() => ColorRandom<TColor, TChannelValue>.MakeRandomValue();
private static TColor MakeRandomColor() => ColorRandom<TColor, TChannel>.MakeRandomColor();
private static TColor MakeRandomColor() => ColorRandom<TColor, TChannelValue>.MakeRandomColor();
}
@@ -88,6 +88,6 @@ namespace AssetRipper.TextureDecoder.Tests.Formats.Generic;
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGB<decimal>), typeof(decimal) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorRGBA<decimal>), typeof(decimal) })]
[TestFixture(TypeArgs = new Type[] { typeof(ColorA<decimal>), typeof(decimal) })]
partial class GenericColorTests<TColor, TChannel>
partial class GenericColorTests<TColor, TChannelValue>
{
}
@@ -3,15 +3,15 @@
namespace AssetRipper.TextureDecoder.Tests.Formats;
internal static class LosslessConversion
{
internal static void Assert<TColor1, TChannel1, TColor2, TChannel2>()
where TChannel1 : unmanaged
where TChannel2 : unmanaged
where TColor1 : unmanaged, IColor<TChannel1>
where TColor2 : unmanaged, IColor<TChannel2>
internal static void Assert<TColor1, TChannelValue1, TColor2, TChannelValue2>()
where TChannelValue1 : unmanaged
where TChannelValue2 : unmanaged
where TColor1 : unmanaged, IColor<TChannelValue1>
where TColor2 : unmanaged, IColor<TChannelValue2>
{
TColor1 original = ColorRandom<TColor1, TChannel1>.MakeRandomColor();
TColor2 converted = original.Convert<TColor1, TChannel1, TColor2, TChannel2>();
TColor1 convertedBack = converted.Convert<TColor2, TChannel2, TColor1, TChannel1>();
TColor1 original = ColorRandom<TColor1, TChannelValue1>.MakeRandomColor();
TColor2 converted = original.Convert<TColor1, TChannelValue1, TColor2, TChannelValue2>();
TColor1 convertedBack = converted.Convert<TColor2, TChannelValue2, TColor1, TChannelValue1>();
NUnit.Framework.Assert.That(convertedBack, Is.EqualTo(original));
}
}
}
@@ -1,29 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<PackageId>AssetRipper.TextureDecoder</PackageId>
<PackageTags>C# Texture</PackageTags>
<RepositoryUrl>https://github.com/AssetRipper/TextureDecoder</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/AssetRipper/TextureDecoder</PackageProjectUrl>
<Copyright>Copyright (c) 2022 - 2023 ds5678</Copyright>
<Description>Managed library for decoding textures in multiple formats.</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>bin\TextureDecoder.xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<PackageId>AssetRipper.TextureDecoder</PackageId>
<PackageTags>C# Texture</PackageTags>
<RepositoryUrl>https://github.com/AssetRipper/TextureDecoder</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/AssetRipper/TextureDecoder</PackageProjectUrl>
<Copyright>Copyright (c) 2022 - 2024 ds5678</Copyright>
<Description>Managed library for decoding textures in multiple formats.</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>bin\TextureDecoder.xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
+27 -13
View File
@@ -883,7 +883,19 @@ namespace AssetRipper.TextureDecoder.Astc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static int GetBits(ReadOnlySpan<byte> input, int bit, int len)
{
return (BinaryPrimitives.ReadInt32LittleEndian(input[(bit / 8)..]) >> (bit % 8)) & ((1 << len) - 1);
ReadOnlySpan<byte> slice = input[(bit / 8)..];
int bitBuffer;
if (slice.Length >= sizeof(int))
{
bitBuffer = BinaryPrimitives.ReadInt32LittleEndian(slice);
}
else
{
Span<byte> temp = stackalloc byte[sizeof(int)];
slice.CopyTo(temp);
bitBuffer = BinaryPrimitives.ReadInt32LittleEndian(temp);
}
return (bitBuffer >> (bit % 8)) & ((1 << len) - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -984,10 +996,12 @@ namespace AssetRipper.TextureDecoder.Astc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static byte SelectColor(int v0, int v1, int weight)
{
return (byte)(((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * 255 + 32768) / 65536);
const int SignAdjustment = short.MaxValue + 1;
const int MaxValue = ushort.MaxValue + 1;
return (byte)(((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * byte.MaxValue + SignAdjustment) / MaxValue);
}
private static readonly byte[] BitReverseTable =
private static ReadOnlySpan<byte> BitReverseTable =>
[
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
@@ -1007,15 +1021,15 @@ namespace AssetRipper.TextureDecoder.Astc
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
];
private static readonly int[] WeightPrecTableA = [0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0];
private static readonly int[] WeightPrecTableB = [0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5];
private static ReadOnlySpan<int> WeightPrecTableA => [0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0];
private static ReadOnlySpan<int> WeightPrecTableB => [0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5];
private static readonly int[] CemTableA = [0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0];
private static readonly int[] CemTableB = [8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1];
private static ReadOnlySpan<int> CemTableA => [0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0];
private static ReadOnlySpan<int> CemTableB => [8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1];
private static readonly int[] DImt = [0, 2, 4, 5, 7];
private static readonly int[] DImq = [0, 3, 5];
private static readonly int[] DITritsTable =
private static ReadOnlySpan<int> DImt => [0, 2, 4, 5, 7];
private static ReadOnlySpan<int> DImq => [0, 3, 5];
private static ReadOnlySpan<int> DITritsTable =>
[
0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2,
0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1,
@@ -1023,14 +1037,14 @@ namespace AssetRipper.TextureDecoder.Astc
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
];
private static readonly int[] DIQuintsTable =
private static ReadOnlySpan<int> DIQuintsTable =>
[
0, 1, 2, 3, 4, 0, 4, 4, 0, 1, 2, 3, 4, 1, 4, 4, 0, 1, 2, 3, 4, 2, 4, 4, 0, 1, 2, 3, 4, 3, 4, 4, 0, 1, 2, 3, 4, 0, 4, 0, 0, 1, 2, 3, 4, 1, 4, 1, 0, 1, 2, 3, 4, 2, 4, 2, 0, 1, 2, 3, 4, 3, 4, 3, 0, 1, 2, 3, 4, 0, 2, 3, 0, 1, 2, 3, 4, 1, 2, 3, 0, 1, 2, 3, 4, 2, 2, 3, 0, 1, 2, 3, 4, 3, 2, 3, 0, 1, 2, 3, 4, 0, 0, 1, 0, 1, 2, 3, 4, 1, 0, 1, 0, 1, 2, 3, 4, 2, 0, 1, 0, 1, 2, 3, 4, 3, 0, 1,
0, 0, 0, 0, 0, 4, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 0, 0, 0, 0, 0, 4, 0, 4, 1, 1, 1, 1, 1, 4, 1, 4, 2, 2, 2, 2, 2, 4, 2, 4, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3,
0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 3, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4,
];
private static readonly int[] DETritsTable = [0, 204, 93, 44, 22, 11, 5];
private static readonly int[] DEQuintsTable = [0, 113, 54, 26, 13, 6];
private static ReadOnlySpan<int> DETritsTable => [0, 204, 93, 44, 22, 11, 5];
private static ReadOnlySpan<int> DEQuintsTable => [0, 113, 54, 26, 13, 6];
}
}
@@ -121,6 +121,7 @@ namespace AssetRipper.TextureDecoder.Atc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static T ReadAtOffset<T>(this ReadOnlySpan<byte> input, int offset) where T : unmanaged
{
ThrowHelper.ThrowIfNotLittleEndian();
return MemoryMarshal.Read<T>(input.Slice(offset));
}
+21 -4
View File
@@ -9,6 +9,22 @@ public static class Bc1
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 8;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
@@ -18,13 +34,14 @@ public static class Bc1
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), output.Slice(outputOffset), width * 4);
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), buffer);
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
inputOffset += BlockSize;
}
}
+21 -4
View File
@@ -9,6 +9,22 @@ public static class Bc2
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 16;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
@@ -18,13 +34,14 @@ public static class Bc2
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
BcHelpers.DecompressBc2(input.Slice(inputOffset), output.Slice(outputOffset), width * 4);
BcHelpers.DecompressBc2(input.Slice(inputOffset), buffer);
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
inputOffset += BlockSize;
}
}
+21 -4
View File
@@ -9,6 +9,22 @@ public static class Bc3
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 16;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
@@ -18,13 +34,14 @@ public static class Bc3
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
BcHelpers.DecompressBc3(input.Slice(inputOffset), output.Slice(outputOffset), width * 4);
BcHelpers.DecompressBc3(input.Slice(inputOffset), buffer);
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
inputOffset += BlockSize;
}
}
+26 -9
View File
@@ -10,6 +10,22 @@ public static class Bc4
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 8;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorR<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
@@ -19,21 +35,22 @@ public static class Bc4
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int bufferSize = width * height * Unsafe.SizeOf<ColorR<byte>>();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
int naturalSize = width * height * PixelSize;
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int bufferOffset = ((i * width) + j) * Unsafe.SizeOf<ColorR<byte>>();
BcHelpers.DecompressBc4(input.Slice(inputOffset), buffer.Slice(bufferOffset), width);
BcHelpers.DecompressBc4(input.Slice(inputOffset), buffer);
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(naturalPixels, width, height, output);
ArrayPool<byte>.Shared.Return(rentedArray);
return inputOffset;
}
+26 -9
View File
@@ -10,6 +10,22 @@ public static class Bc5
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 16;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRG<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
@@ -19,21 +35,22 @@ public static class Bc5
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
int bufferSize = width * height * Unsafe.SizeOf<ColorRG<byte>>();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
int naturalSize = width * height * PixelSize;
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int bufferOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRG<byte>>();
BcHelpers.DecompressBc5(input.Slice(inputOffset), buffer.Slice(bufferOffset), width * 2);
BcHelpers.DecompressBc5(input.Slice(inputOffset), buffer);
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(naturalPixels, width, height, output);
ArrayPool<byte>.Shared.Return(rentedArray);
return inputOffset;
}
+29 -10
View File
@@ -10,6 +10,23 @@ public static class Bc6h
/// The size of an encoded block, in bytes.
/// </summary>
public const int BlockSize = 16;
/// <summary>
/// The width of a decoded block, in pixels.
/// </summary>
private const int BlockWidth = 4;
/// <summary>
/// The height of a decoded block, in pixels.
/// </summary>
private const int BlockHeight = 4;
/// <summary>
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGB<Half>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, bool isSigned, out byte[] output)
{
@@ -19,25 +36,27 @@ public static class Bc6h
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, bool isSigned, Span<byte> output)
{
int bufferSize = width * height * Unsafe.SizeOf<ColorRGB<Half>>();
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
int naturalSize = width * height * PixelSize;
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += 4)
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += 4)
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGB<Half>>();
BcHelpers.DecompressBc6h(
input.Slice(inputOffset, BlockSize),
buffer.Slice(outputOffset),
width * 3,
buffer,
isSigned);
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
inputOffset += BlockSize;
}
}
RgbConverter.Convert<ColorRGB<Half>, Half, ColorBGRA32, byte>(buffer, width, height, output);
ArrayPool<byte>.Shared.Return(bufferArray);
RgbConverter.Convert<ColorRGB<Half>, Half, ColorBGRA32, byte>(naturalPixels, width, height, output);
ArrayPool<byte>.Shared.Return(rentedArray);
return inputOffset;
}
+2 -2
View File
@@ -211,7 +211,7 @@ internal static class Bc6hTables
]
];
internal static int[] AWeight3 { get; } = [0, 9, 18, 27, 37, 46, 55, 64];
internal static ReadOnlySpan<int> AWeight3 => [0, 9, 18, 27, 37, 46, 55, 64];
internal static int[] AWeight4 { get; } = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
internal static ReadOnlySpan<int> AWeight4 => [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
}
+10 -3
View File
@@ -21,6 +21,10 @@ public static class Bc7
/// The size of the natural pixel type.
/// </summary>
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
/// <summary>
/// The size of a decoded block, in bytes.
/// </summary>
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
@@ -30,16 +34,19 @@ public static class Bc7
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
int inputOffset = 0;
for (int i = 0; i < height; i += BlockHeight)
{
for (int j = 0; j < width; j += BlockWidth)
{
int outputOffset = ((i * width) + j) * PixelSize;
BcHelpers.DecompressBc7(
input.Slice(inputOffset, BlockSize),
output.Slice(outputOffset),
width * 4);
buffer);
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
inputOffset += BlockSize;
}
}
+3 -3
View File
@@ -789,11 +789,11 @@ internal static class Bc7Tables
]
];
internal static int[] AWeight2 { get; } = [0, 21, 43, 64];
internal static ReadOnlySpan<int> AWeight2 => [0, 21, 43, 64];
internal static int[] AWeight3 { get; } = [0, 9, 18, 27, 37, 46, 55, 64];
internal static ReadOnlySpan<int> AWeight3 => [0, 9, 18, 27, 37, 46, 55, 64];
internal static int[] AWeight4 { get; } = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
internal static ReadOnlySpan<int> AWeight4 => [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
internal const byte bcdec_bc7_sModeHasPBits = 0b11001011;
}
+145 -94
View File
@@ -1,47 +1,56 @@
using System.Buffers.Binary;
using System.Diagnostics;
namespace AssetRipper.TextureDecoder.Bc;
internal static class BcHelpers
{
public static void DecompressBc1(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc1(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc1.DecodedBlockSize);
int destinationPitch = 4 * 4;
ColorBlock(compressedBlock, decompressedBlock, destinationPitch, 0);
}
public static void DecompressBc2(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc2(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc2.DecodedBlockSize);
int destinationPitch = 4 * 4;
ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
SharpAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch);
}
public static void DecompressBc3(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc3(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc3.DecodedBlockSize);
int destinationPitch = 4 * 4;
ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
SmoothAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch, 4);
}
public static void DecompressBc4(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc4(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc4.DecodedBlockSize);
int destinationPitch = 4 * 1;
SmoothAlphaBlock(compressedBlock, decompressedBlock, destinationPitch, 1);
}
public static void DecompressBc5(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc5(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
Debug.Assert(decompressedBlock.Length == Bc5.DecodedBlockSize);
int destinationPitch = 4 * 2;
SmoothAlphaBlock(compressedBlock, decompressedBlock, destinationPitch, 2);
SmoothAlphaBlock(compressedBlock.Slice(8), decompressedBlock.Slice(1), destinationPitch, 2);
}
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch, bool isSigned)
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, bool isSigned)
{
BitStream bstream = new()
{
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock),
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong)))
};
Span<int> r = stackalloc int[4]; // wxyz
Span<int> g = stackalloc int[4];
Span<int> b = stackalloc int[4];
Debug.Assert(decompressedBlock.Length == Bc6h.DecodedBlockSize);
BitStream bstream = new(compressedBlock);
Span<uint> r = stackalloc uint[4]; // wxyz
Span<uint> g = stackalloc uint[4];
Span<uint> b = stackalloc uint[4];
int decompressedOffset = 0;
@@ -52,7 +61,7 @@ internal static class BcHelpers
int mode;
//modes >= 11 (10 in my code) are using 0 one, others will read it from the bitstream
int partition;
uint partition;
switch (ReadBc6hModeBits(ref bstream))
{
@@ -452,15 +461,7 @@ internal static class BcHelpers
// Do not use these in your encoder. If the hardware is passed blocks
// with one of these modes specified, the resulting decompressed block
// must contain all zeroes in all channels except for the alpha channel.
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
Span<byte> pixel = decompressedBlock.Slice(decompressedOffset + (j * 3 * sizeof(ushort)), 3 * sizeof(ushort));
pixel.Clear();
}
decompressedOffset += destinationPitch * sizeof(ushort);
}
decompressedBlock.Clear();
return;
}
@@ -477,7 +478,7 @@ internal static class BcHelpers
// Mode 11 (like Mode 10) does not use delta compression,
// and instead stores both color endpoints explicitly.
if ((mode != 9 && mode != 10) || isSigned)
if ((mode is not 9 and not 10) || isSigned)
{
for (int i = 1; i < (numPartitions + 1) * 2; ++i)
{
@@ -487,7 +488,7 @@ internal static class BcHelpers
}
}
if (mode != 9 && mode != 10)
if (mode is not 9 and not 10)
{
for (int i = 1; i < (numPartitions + 1) * 2; ++i)
{
@@ -520,7 +521,7 @@ internal static class BcHelpers
}
partitionSet &= 0x01;
int index = bstream.ReadBits(indexBits);
int index = bstream.ReadBits(indexBits).ToSigned();
int ep_i = (partitionSet * 2);
@@ -539,15 +540,15 @@ internal static class BcHelpers
BinaryPrimitives.WriteUInt16LittleEndian(decompressedBlock.Slice(bOffset), bFinal);
}
decompressedOffset += destinationPitch * sizeof(ushort);
decompressedOffset += 4 * 3 * sizeof(ushort);
}
static int ReadBc6hModeBits(ref BitStream bstream)
static uint ReadBc6hModeBits(ref BitStream bstream)
{
int twoBits = bstream.ReadBits(2);
uint twoBits = bstream.ReadBits(2);
if (twoBits > 1)
{
int threeBits = bstream.ReadBits(3);
uint threeBits = bstream.ReadBits(3);
return (threeBits << 2) | twoBits;
}
else
@@ -557,61 +558,54 @@ internal static class BcHelpers
}
}
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
{
BitStream bstream = new()
{
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock),
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong)))
};
Span2D<int> endpoints = new(stackalloc int[6 * 4], 6, 4);
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
Debug.Assert(decompressedBlock.Length == Bc7.DecodedBlockSize);
int decompressedOffset = 0;
BitStream bstream = new(compressedBlock);
Span2D<uint> endpoints = new(stackalloc uint[6 * 4], 6, 4);
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
int mode = ReadBc7Mode(ref bstream);
// unexpected mode, clear the block (transparent black)
if (mode >= 8)
{
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
decompressedBlock[decompressedOffset + (j * 4) + 0] = 0;
decompressedBlock[decompressedOffset + (j * 4) + 1] = 0;
decompressedBlock[decompressedOffset + (j * 4) + 2] = 0;
decompressedBlock[decompressedOffset + (j * 4) + 3] = 0;
}
decompressedOffset += destinationPitch;
}
decompressedBlock.Clear();
return;
}
int partition = 0;
uint partition = 0;
int numPartitions = 1;
int rotation = 0;
int indexSelectionBit = 0;
uint rotation = 0;
uint indexSelectionBit = 0;
if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 7)
switch (mode)
{
numPartitions = (mode == 0 || mode == 2) ? 3 : 2;
partition = bstream.ReadBits((mode == 0) ? 4 : 6);
case 0:
numPartitions = 3;
partition = bstream.ReadBits(4);
break;
case 1 or 3 or 7:
numPartitions = 2;
partition = bstream.ReadBits(6);
break;
case 2:
numPartitions = 3;
partition = bstream.ReadBits(6);
break;
case 4:
rotation = bstream.ReadBits(2);
indexSelectionBit = bstream.ReadBit();
break;
case 5:
rotation = bstream.ReadBits(2);
break;
}
int numEndpoints = numPartitions * 2;
if (mode == 4 || mode == 5)
{
rotation = bstream.ReadBits(2);
if (mode == 4)
{
indexSelectionBit = bstream.ReadBit();
}
}
// Extract endpoints
// RGB
for (int i = 0; i < 3; ++i)
@@ -646,8 +640,8 @@ internal static class BcHelpers
// if P-bit is shared
if (mode == 1)
{
int i = bstream.ReadBit();
int j = bstream.ReadBit();
uint i = bstream.ReadBit();
uint j = bstream.ReadBit();
// rgb component-wise insert pbits
for (int k = 0; k < 3; ++k)
@@ -663,7 +657,7 @@ internal static class BcHelpers
// unique P-bit per endpoint
for (int i = 0; i < numEndpoints; ++i)
{
int j = bstream.ReadBit();
uint j = bstream.ReadBit();
for (int k = 0; k < 4; ++k)
{
endpoints[i, k] |= j;
@@ -705,10 +699,25 @@ internal static class BcHelpers
}
// Determine weights tables
int indexBits = (mode == 0 || mode == 1) ? 3 : ((mode == 6) ? 4 : 2);
int indexBits2 = (mode == 4) ? 3 : ((mode == 5) ? 2 : 0);
int[] weights = (indexBits == 2) ? Bc7Tables.AWeight2 : ((indexBits == 3) ? Bc7Tables.AWeight3 : Bc7Tables.AWeight4);
int[] weights2 = (indexBits2 == 2) ? Bc7Tables.AWeight2 : Bc7Tables.AWeight3;
int indexBits = mode switch
{
0 or 1 => 3,
6 => 4,
_ => 2
};
int indexBits2 = mode switch
{
4 => 3,
5 => 2,
_ => 0
};
ReadOnlySpan<int> weights = indexBits switch
{
2 => Bc7Tables.AWeight2,
3 => Bc7Tables.AWeight3,
_ => Bc7Tables.AWeight4
};
ReadOnlySpan<int> weights2 = (indexBits2 == 2) ? Bc7Tables.AWeight2 : Bc7Tables.AWeight3;
// Quite inconvenient that indices aren't interleaved so we have to make 2 passes here
// Pass #1: collecting color indices
@@ -726,7 +735,7 @@ internal static class BcHelpers
indexBits--;
}
indices[i, j] = bstream.ReadBits(indexBits);
indices[i, j] = bstream.ReadBits(indexBits).ToSigned();
}
}
@@ -742,10 +751,10 @@ internal static class BcHelpers
int index = indices[i, j];
int r;
int g;
int b;
int a;
uint r;
uint g;
uint b;
uint a;
if (indexBits2 == 0)
{
@@ -756,7 +765,7 @@ internal static class BcHelpers
}
else
{
int index2 = bstream.ReadBits((i | j) != 0 ? indexBits2 : (indexBits2 - 1));
int index2 = bstream.ReadBits((i | j) != 0 ? indexBits2 : (indexBits2 - 1)).ToSigned();
// The index value for interpolating color comes from the secondary index bits for the texel
// if the mode has an index selection bit and its value is one, and from the primary index bits otherwise.
// The alpha index comes from the secondary index bits if the block has a secondary index
@@ -800,23 +809,23 @@ internal static class BcHelpers
break;
}
decompressedBlock[decompressedOffset + (j * 4) + 0] = (byte)r;
decompressedBlock[decompressedOffset + (j * 4) + 1] = (byte)g;
decompressedBlock[decompressedOffset + (j * 4) + 2] = (byte)b;
decompressedBlock[decompressedOffset + (j * 4) + 3] = (byte)a;
int offset = (i * 16) + (j * 4);
decompressedBlock[offset + 0] = (byte)r;
decompressedBlock[offset + 1] = (byte)g;
decompressedBlock[offset + 2] = (byte)b;
decompressedBlock[offset + 3] = (byte)a;
}
decompressedOffset += destinationPitch;
}
static int ReadBc7Mode(ref BitStream bstream)
{
int i = 0;
while (i < 8 && bstream.ReadBit() == 0)
{
i++;
}
return i;
// Read bits until we find a non-zero bit, up to 8 bits.
// The mode is the number of bits read.
uint eightBits = bstream.PeakBits(8);
int zeros = BitOperations.TrailingZeroCount(eightBits);
int bitCount = zeros >= 8 ? 8 : zeros + 1;
bstream.Advance(bitCount);
return int.Min(zeros, 8);
}
}
@@ -949,6 +958,11 @@ internal static class BcHelpers
return (val << (32 - bits)) >> (32 - bits);
}
private static uint ExtendSign(uint val, int bits)
{
return ExtendSign(val.ToSigned(), bits).ToUnsigned();
}
public static int TransformInverse(int val, int a0, int bits, bool isSigned)
{
// If the precision of A0 is "p" bits, then the transform algorithm is:
@@ -961,6 +975,11 @@ internal static class BcHelpers
return val;
}
private static uint TransformInverse(uint val, uint a0, int bits, bool isSigned)
{
return TransformInverse(val.ToSigned(), a0.ToSigned(), bits, isSigned).ToUnsigned();
}
/// <summary>
/// Essentially copy-paste from documentation
/// </summary>
@@ -1028,11 +1047,21 @@ internal static class BcHelpers
return unq;
}
private static uint Unquantize(uint val, int bits, bool isSigned)
{
return Unquantize(val.ToSigned(), bits, isSigned).ToUnsigned();
}
public static int Interpolate(int a, int b, ReadOnlySpan<int> weights, int index)
{
return ((a * (64 - weights[index])) + (b * weights[index]) + 32) >> 6;
}
private static uint Interpolate(uint a, uint b, ReadOnlySpan<int> weights, int index)
{
return Interpolate(a.ToSigned(), b.ToSigned(), weights, index).ToUnsigned();
}
public static ushort FinishUnquantize(int val, bool isSigned)
{
if (!isSigned)
@@ -1041,7 +1070,7 @@ internal static class BcHelpers
}
else
{
val = (val < 0) ? -(((-val) * 31) >> 5) : (val * 31) >> 5; // scale the magnitude by 31 / 32
val = (val < 0) ? (((-val) * 31) >> 5) : (val * 31) >> 5; // scale the magnitude by 31 / 32
int s = 0;
if (val < 0)
{
@@ -1052,8 +1081,30 @@ internal static class BcHelpers
}
}
public static void SwapValues(ref int a, ref int b)
private static ushort FinishUnquantize(uint val, bool isSigned)
{
return FinishUnquantize(val.ToSigned(), isSigned);
}
public static void SwapValues(ref uint a, ref uint b)
{
(a, b) = (b, a);
}
/// <summary>
/// This is for copying a decoded block to the output buffer. It handles the pitch and non-4x4 blocks (if necessary).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void CopyBufferToOutput(Span<byte> buffer, Span<byte> output, int width, int height, int w, int h, int blockWidth, int blockHeight, int pixelSize)
{
int hEnd = Math.Min(h + blockHeight, height);
int wEnd = Math.Min(w + blockWidth, width);
for (int i = h; i < hEnd; i++)
{
int outputOffset = ((i * width) + w) * pixelSize;
int outputBufferOffset = ((i - h) * blockWidth) * pixelSize;
int copyLength = (wEnd - w) * pixelSize;
buffer.Slice(outputBufferOffset, copyLength).CopyTo(output.Slice(outputOffset, copyLength));
}
}
}
+54 -28
View File
@@ -1,27 +1,59 @@
namespace AssetRipper.TextureDecoder.Bc;
using System.Buffers.Binary;
internal struct BitStream
namespace AssetRipper.TextureDecoder.Bc;
internal ref struct BitStream
{
public ulong low;
public ulong high;
public int ReadBits(int numBits)
private UInt128 value;
private readonly uint BottomBits
{
uint mask = (uint)((1 << numBits) - 1);
// Read the low N bits
uint bits = (uint)(this.low & mask);
this.low >>= numBits;
// Put the low N bits of "high" into the high 64-N bits of "low".
this.low |= (this.high & mask) << ((sizeof(ulong) * 8) - numBits);
this.high >>= numBits;
return (int)bits;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => unchecked((uint)this.value);
}
public int ReadBit()
public BitStream(UInt128 value)
{
return this.ReadBits(1);
this.value = value;
}
public BitStream(ReadOnlySpan<byte> span) : this(BinaryPrimitives.ReadUInt128LittleEndian(span))
{
}
public BitStream(ulong low, ulong high)
{
value = new UInt128(high, low);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public readonly uint PeakBits(int numBits)
{
uint mask = (1u << numBits) - 1u;
uint bits = BottomBits & mask;
return bits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void Advance(int numBits)
{
this.value >>= numBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public uint ReadBits(int numBits)
{
uint bits = PeakBits(numBits);
Advance(numBits);
return bits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public uint ReadBit()
{
uint result = BottomBits & 1u;
Advance(1);
return result;
}
/// <summary>
@@ -29,17 +61,11 @@ internal struct BitStream
/// </summary>
/// <param name="numBits"></param>
/// <returns></returns>
public int ReadBitsReversed(int numBits)
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public uint ReadBitsReversed(int numBits)
{
int bits = this.ReadBits(numBits);
uint bits = this.ReadBits(numBits);
// Reverse the bits.
int result = 0;
while (numBits-- != 0)
{
result <<= 1;
result |= bits & 1;
bits >>= 1;
}
return result;
return bits.ReverseBits() >> (32 - numBits);
}
}
@@ -0,0 +1,24 @@
namespace AssetRipper.TextureDecoder.Bc;
internal static class NumericExtensions
{
public static uint ReverseBits(this uint n)
{
n = (n >> 1) & 0x55555555 | (n << 1) & 0xAAAAAAAA;
n = (n >> 2) & 0x33333333 | (n << 2) & 0xCCCCCCCC;
n = (n >> 4) & 0x0F0F0F0F | (n << 4) & 0xF0F0F0F0;
n = (n >> 8) & 0x00FF00FF | (n << 8) & 0xFF00FF00;
n = (n >> 16) & 0x0000FFFF | (n << 16) & 0xFFFF0000;
return n;
}
public static uint ToUnsigned(this int value)
{
return unchecked((uint)value);
}
public static int ToSigned(this uint value)
{
return unchecked((int)value);
}
}
+53 -50
View File
@@ -36,50 +36,51 @@ namespace AssetRipper.TextureDecoder.Dxt
/// Decompress a DXT1 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT1<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return DecompressDXT1<TOutputColor, TOutputChannel>(input, width, height, output);
return DecompressDXT1<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
/// <summary>
/// Decompress a DXT1 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT1<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
return DecompressDXT1<TOutputColor, TOutputChannel>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT1<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
/// <summary>
/// Decompress a DXT1 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT1<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT1<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
@@ -97,17 +98,17 @@ namespace AssetRipper.TextureDecoder.Dxt
int q1 = input[offset + 2] | input[offset + 3] << 8;
Rgb565(q0, out byte r0, out byte g0, out byte b0);
Rgb565(q1, out byte r1, out byte g1, out byte b1);
colors[0].SetConvertedChannels<TOutputColor, TOutputChannel, byte>(r0, g0, b0);
colors[1].SetConvertedChannels<TOutputColor, TOutputChannel, byte>(r1, g1, b1);
colors[0].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>(r0, g0, b0);
colors[1].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>(r1, g1, b1);
if (q0 > q1)
{
colors[2].SetConvertedChannels<TOutputColor, TOutputChannel, byte>((byte)((r0 * 2 + r1) / 3), (byte)((g0 * 2 + g1) / 3), (byte)((b0 * 2 + b1) / 3));
colors[3].SetConvertedChannels<TOutputColor, TOutputChannel, byte>((byte)((r0 + r1 * 2) / 3), (byte)((g0 + g1 * 2) / 3), (byte)((b0 + b1 * 2) / 3));
colors[2].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>((byte)((r0 * 2 + r1) / 3), (byte)((g0 * 2 + g1) / 3), (byte)((b0 * 2 + b1) / 3));
colors[3].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>((byte)((r0 + r1 * 2) / 3), (byte)((g0 + g1 * 2) / 3), (byte)((b0 + b1 * 2) / 3));
}
else
{
colors[2].SetConvertedChannels<TOutputColor, TOutputChannel, byte>((byte)((r0 + r1) / 2), (byte)((g0 + g1) / 2), (byte)((b0 + b1) / 2));
colors[3].SetBlack<TOutputColor, TOutputChannel>();
colors[2].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>((byte)((r0 + r1) / 2), (byte)((g0 + g1) / 2), (byte)((b0 + b1) / 2));
colors[3].SetBlack<TOutputColor, TOutputChannelValue>();
}
uint d = ToUInt32(input, offset + 4);
@@ -157,50 +158,51 @@ namespace AssetRipper.TextureDecoder.Dxt
/// Decompress a DXT3 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT3<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT3<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return DecompressDXT3<TOutputColor, TOutputChannel>(input, width, height, output);
return DecompressDXT3<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
/// <summary>
/// Decompress a DXT1 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT3<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT3<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
return DecompressDXT3<TOutputColor, TOutputChannel>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT3<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
/// <summary>
/// Decompress a DXT3 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT3<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT3<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
@@ -244,7 +246,7 @@ namespace AssetRipper.TextureDecoder.Dxt
uint d = ToUInt32(input, offset + 12);
for (int i = 0; i < 16; i++, d >>= 2)
{
buffer[i].SetConvertedChannels<TOutputColor, TOutputChannel, byte>(colors[unchecked((int)(d & 3))], alphas[i]);
buffer[i].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>(colors[unchecked((int)(d & 3))], alphas[i]);
}
int clen = s < bcw - 1 ? 4 : clen_last;
@@ -288,50 +290,51 @@ namespace AssetRipper.TextureDecoder.Dxt
/// Decompress a DXT5 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT5<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT5<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return DecompressDXT5<TOutputColor, TOutputChannel>(input, width, height, output);
return DecompressDXT5<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
/// <summary>
/// Decompress a DXT1 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT5<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT5<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
return DecompressDXT5<TOutputColor, TOutputChannel>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressDXT5<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
/// <summary>
/// Decompress a DXT5 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressDXT5<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressDXT5<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
@@ -390,7 +393,7 @@ namespace AssetRipper.TextureDecoder.Dxt
uint dc = ToUInt32(input, offset + 12);
for (int i = 0; i < 16; i++, da >>= 3, dc >>= 2)
{
buffer[i].SetConvertedChannels<TOutputColor, TOutputChannel, byte>(colors[unchecked((int)(dc & 3))], alphas[unchecked((int)(da & 7))]);
buffer[i].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>(colors[unchecked((int)(dc & 3))], alphas[unchecked((int)(da & 7))]);
}
int clen = s < bcw - 1 ? 4 : clen_last;
+20 -9
View File
@@ -10,6 +10,7 @@ namespace AssetRipper.TextureDecoder.Etc
public static int DecompressETC(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -50,6 +51,7 @@ namespace AssetRipper.TextureDecoder.Etc
public static int DecompressETC2(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -90,6 +92,7 @@ namespace AssetRipper.TextureDecoder.Etc
public static int DecompressETC2A1(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -130,6 +133,7 @@ namespace AssetRipper.TextureDecoder.Etc
public static int DecompressETC2A8(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
if (input.Length < requiredLength)
{
@@ -171,6 +175,7 @@ namespace AssetRipper.TextureDecoder.Etc
public static int DecompressEACRUnsigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -215,6 +220,7 @@ namespace AssetRipper.TextureDecoder.Etc
public static int DecompressEACRSigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
if (input.Length < requiredLength)
{
@@ -259,6 +265,7 @@ namespace AssetRipper.TextureDecoder.Etc
public static int DecompressEACRGUnsigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int bcw = (width + 3) / 4;
int bch = (height + 3) / 4;
@@ -304,6 +311,7 @@ namespace AssetRipper.TextureDecoder.Etc
public static int DecompressEACRGSigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
if (input.Length < requiredLength)
{
@@ -741,6 +749,7 @@ namespace AssetRipper.TextureDecoder.Etc
private static void DecodeEtc2a8Block(ReadOnlySpan<byte> input, Span<uint> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int @base = input[0];
int data1 = input[1];
int mul = data1 >> 4;
@@ -761,6 +770,7 @@ namespace AssetRipper.TextureDecoder.Etc
private static void DecodeEacUnsignedBlock(ReadOnlySpan<byte> input, Span<uint> output, int channel)
{
ThrowHelper.ThrowIfNotLittleEndian();
int @base = input[0];
int data1 = input[1];
int mul = data1 >> 4;
@@ -778,6 +788,7 @@ namespace AssetRipper.TextureDecoder.Etc
private static void DecodeEacSignedBlock(ReadOnlySpan<byte> input, Span<uint> output, int channel)
{
ThrowHelper.ThrowIfNotLittleEndian();
int @base = 127 + unchecked((sbyte)input[0]);
int data1 = input[1];
int mul = data1 >> 4;
@@ -805,7 +816,7 @@ namespace AssetRipper.TextureDecoder.Etc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void DecodeEac11Block(Span<byte> output, int @base, int ti, int mul, ulong l)
{
ReadOnlySpan<sbyte> table = Etc2AlphaModTable.AsSpan(ti * 8, 8);
ReadOnlySpan<sbyte> table = Etc2AlphaModTable.Slice(ti * 8, 8);
for (int i = 0; i < 16; i++, l >>= 3)
{
int val = @base + mul * table[unchecked((int)(l & 0b111))];
@@ -849,14 +860,14 @@ namespace AssetRipper.TextureDecoder.Etc
return data[7] | (uint)data[6] << 8 | (uint)data[5] << 16 | (uint)data[4] << 24 | (ulong)data[3] << 32 | (ulong)data[2] << 40;
}
private static readonly byte[] WriteOrderTable = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
private static readonly byte[] WriteOrderTableRev = [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0];
private static readonly int[] Etc1SubblockTable =
private static ReadOnlySpan<byte> WriteOrderTable => [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
private static ReadOnlySpan<byte> WriteOrderTableRev => [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0];
private static ReadOnlySpan<int> Etc1SubblockTable =>
[
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
];
private static readonly int[] Etc1ModifierTable =
private static ReadOnlySpan<int> Etc1ModifierTable =>
[
2, 8, -2, -8,
5, 17, -5, -17,
@@ -867,7 +878,7 @@ namespace AssetRipper.TextureDecoder.Etc
33, 106, -33, -106,
47, 183, -47, -183,
];
private static readonly int[] PunchthroughModifierTable =
private static ReadOnlySpan<int> PunchthroughModifierTable =>
[
0, 8, 0, -8,
0, 17, 0, -17,
@@ -878,8 +889,8 @@ namespace AssetRipper.TextureDecoder.Etc
0, 106, 0, -106,
0, 183, 0, -183,
];
private static readonly byte[] Etc2DistanceTable = [3, 6, 11, 16, 23, 32, 41, 64];
private static readonly sbyte[] Etc2AlphaModTable =
private static ReadOnlySpan<byte> Etc2DistanceTable => [3, 6, 11, 16, 23, 32, 41, 64];
private static ReadOnlySpan<sbyte> Etc2AlphaModTable =>
[
-3, -6, -9, -15, 2, 5, 8, 14,
-3, -7, -10, -13, 2, 6, 9, 12,
@@ -898,6 +909,6 @@ namespace AssetRipper.TextureDecoder.Etc
-4, -6, -8, -9, 3, 5, 7, 8,
-3, -5, -7, -9, 2, 4, 6, 8,
];
private static readonly uint[] PunchthroughMaskTable = [0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF];
private static ReadOnlySpan<uint> PunchthroughMaskTable => [0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF];
}
}
@@ -0,0 +1,112 @@
using System.Buffers;
using System.Text;
namespace AssetRipper.TextureDecoder.Exr;
internal static class BinaryWriterExtensions
{
public static void WriteLengthPrefixedString(this BinaryWriter writer, string value)
{
int length = Encoding.UTF8.GetByteCount(value);
writer.Write(length);
byte[]? rentedArray;
scoped Span<byte> buffer;
if (length <= 1024)
{
rentedArray = null;
buffer = stackalloc byte[length];
}
else
{
rentedArray = ArrayPool<byte>.Shared.Rent(length);
buffer = rentedArray.AsSpan(0, length);
}
Encoding.UTF8.GetBytes(value, buffer);
writer.Write(buffer);
if (rentedArray is not null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
}
public static void WriteNullTerminatedString(this BinaryWriter writer, string value)
{
int length = Encoding.UTF8.GetByteCount(value);
byte[]? rentedArray;
scoped Span<byte> buffer;
if (length <= 1024)
{
rentedArray = null;
buffer = stackalloc byte[length];
}
else
{
rentedArray = ArrayPool<byte>.Shared.Rent(length);
buffer = rentedArray.AsSpan(0, length);
}
Encoding.UTF8.GetBytes(value, buffer);
writer.Write(buffer);
if (rentedArray is not null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
writer.WriteNullByte();
}
public static void WriteNullTerminatedString(this BinaryWriter writer, ReadOnlySpan<byte> utf8String)
{
writer.Write(utf8String);
writer.WriteNullByte();
}
public static void WriteNullByte(this BinaryWriter writer)
{
writer.Write(default(byte));
}
public static void Write<T>(this BinaryWriter writer, T value) where T : unmanaged, Enum
{
switch (Unsafe.SizeOf<T>())
{
case sizeof(byte):
writer.Write(Unsafe.As<T, byte>(ref value));
break;
case sizeof(ushort):
writer.Write(Unsafe.As<T, ushort>(ref value));
break;
case sizeof(uint):
writer.Write(Unsafe.As<T, uint>(ref value));
break;
case sizeof(ulong):
writer.Write(Unsafe.As<T, ulong>(ref value));
break;
}
}
public static void WriteAttribute<T>(this BinaryWriter writer, ReadOnlySpan<byte> attributeName, T attributeValue) where T : IExrDataType
{
writer.Write(attributeName);
writer.WriteNullByte();
writer.Write(T.TypeName);
writer.WriteNullByte();
writer.Write(attributeValue.Size);
attributeValue.Write(writer);
}
public static void WriteAttribute<T>(this BinaryWriter writer, ReadOnlySpan<byte> attributeName, ReadOnlySpan<byte> attributeType, T attributeValue) where T : unmanaged, Enum
{
writer.Write(attributeName);
writer.WriteNullByte();
writer.Write(attributeType);
writer.WriteNullByte();
writer.Write(Unsafe.SizeOf<T>());
writer.Write(attributeValue);
}
}
@@ -0,0 +1,22 @@
namespace AssetRipper.TextureDecoder.Exr;
public static class ExrAttributes
{
public static ReadOnlySpan<byte> Channels => "channels"u8;
public static ReadOnlySpan<byte> Compression => "compression"u8;
public static ReadOnlySpan<byte> DataWindow => "dataWindow"u8;
public static ReadOnlySpan<byte> DisplayWindow => "displayWindow"u8;
public static ReadOnlySpan<byte> LineOrder => "lineOrder"u8;
public static ReadOnlySpan<byte> PixelAspectRatio => "pixelAspectRatio"u8;
public static ReadOnlySpan<byte> ScreenWindowCenter => "screenWindowCenter"u8;
public static ReadOnlySpan<byte> ScreenWindowWidth => "screenWindowWidth"u8;
public static ReadOnlySpan<byte> Tiles => "tiles"u8;
}
@@ -0,0 +1,16 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrBox2F(float XMin, float YMin, float XMax, float YMax) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "box2f"u8;
public int Size => sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float);
public void Write(BinaryWriter writer)
{
writer.Write(XMin);
writer.Write(YMin);
writer.Write(XMax);
writer.Write(YMax);
}
}
@@ -0,0 +1,16 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrBox2I(int XMin, int YMin, int XMax, int YMax) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "box2i"u8;
public int Size => sizeof(int) + sizeof(int) + sizeof(int) + sizeof(int);
public void Write(BinaryWriter writer)
{
writer.Write(XMin);
writer.Write(YMin);
writer.Write(XMax);
writer.Write(YMax);
}
}
@@ -0,0 +1,32 @@
using System.Text;
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrChannel(string Name, ExrPixelType PixelType, bool PLinear, int XSampling, int YSampling)
{
public int Size
{
get
{
return Encoding.UTF8.GetByteCount(Name)
+ sizeof(byte)//Null terminator
+ sizeof(ExrPixelType)
+ sizeof(bool)
+ 3 * sizeof(byte)//Reserved bytes
+ sizeof(int)
+ sizeof(int);
}
}
public void Write(BinaryWriter writer)
{
writer.WriteNullTerminatedString(Name);
writer.Write(PixelType);
writer.Write(PLinear);
writer.WriteNullByte();
writer.WriteNullByte();
writer.WriteNullByte();
writer.Write(XSampling);
writer.Write(YSampling);
}
}
@@ -0,0 +1,17 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrChannelList(ExrChannel[] Channels) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "chlist"u8;
public int Size => Channels.Sum(c => c.Size);
public void Write(BinaryWriter writer)
{
foreach (ExrChannel channel in Channels)
{
channel.Write(writer);
}
writer.WriteNullByte();
}
}
@@ -0,0 +1,13 @@
namespace AssetRipper.TextureDecoder.Exr;
public enum ExrCompression : byte
{
None = 0,
RLE = 1,
ZIPS = 2,
ZIP = 3,
PIZ = 4,
PXR24 = 5,
B44 = 6,
B44A = 7,
}
@@ -0,0 +1,17 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrDouble(double Value) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "double"u8;
public int Size => sizeof(double);
public void Write(BinaryWriter writer)
{
writer.Write(Value);
}
public static implicit operator double(ExrDouble value) => value.Value;
public static implicit operator ExrDouble(double value) => new(value);
}
@@ -0,0 +1,14 @@
namespace AssetRipper.TextureDecoder.Exr;
public enum ExrImageFormat
{
ScanLines,
Tiles,
}
public static class ExrImageFormatExtensions
{
public static byte ToVersionByte(this ExrImageFormat format) => format switch
{
ExrImageFormat.Tiles => 0x02,
_ => 0x00,
};
}
@@ -0,0 +1,17 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrInt32(int Value) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "int"u8;
public int Size => sizeof(int);
public void Write(BinaryWriter writer)
{
writer.Write(Value);
}
public static implicit operator int(ExrInt32 value) => value.Value;
public static implicit operator ExrInt32(int value) => new(value);
}
@@ -0,0 +1,11 @@
namespace AssetRipper.TextureDecoder.Exr;
[Flags]
public enum ExrLevelMode : byte
{
OneLevel = 0,
MipMapLevels = 1,
RipMapLevels = 2,
RoundDown = 0,
RoundUp = 16,
}
@@ -0,0 +1,8 @@
namespace AssetRipper.TextureDecoder.Exr;
public enum ExrLineOrder : byte
{
IncreasingY = 0,
DecreasingY = 1,
RandomY = 2,
}
@@ -0,0 +1,8 @@
namespace AssetRipper.TextureDecoder.Exr;
public enum ExrPixelType : int
{
UInt = 0,
Half = 1,
Single = 2,
}
@@ -0,0 +1,17 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrSingle(float Value) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "float"u8;
public int Size => sizeof(float);
public void Write(BinaryWriter writer)
{
writer.Write(Value);
}
public static implicit operator float(ExrSingle value) => value.Value;
public static implicit operator ExrSingle(float value) => new(value);
}
@@ -0,0 +1,16 @@
using System.Text;
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrString(string Value) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "string"u8;
public int Size => sizeof(int) + Encoding.UTF8.GetByteCount(Value);
public void Write(BinaryWriter writer) => writer.WriteLengthPrefixedString(Value);
public static implicit operator string(ExrString value) => value.Value;
public static implicit operator ExrString(string value) => new(value);
}
@@ -0,0 +1,26 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrVector2(Vector2 Value) : IExrDataType
{
public ExrVector2(float x, float y) : this(new Vector2(x, y))
{
}
public int Size => sizeof(float) + sizeof(float);
public float X => Value.X;
public float Y => Value.Y;
public static ReadOnlySpan<byte> TypeName => "v2f"u8;
public void Write(BinaryWriter writer)
{
writer.Write(X);
writer.Write(Y);
}
public static implicit operator Vector2(ExrVector2 value) => value.Value;
public static implicit operator ExrVector2(Vector2 value) => new(value);
}
+225
View File
@@ -0,0 +1,225 @@
using AssetRipper.TextureDecoder.Rgb;
namespace AssetRipper.TextureDecoder.Exr;
public static class ExrWriter
{
public static ReadOnlySpan<byte> Magic => [0x76, 0x2f, 0x31, 0x01];
public static void Write<TColor, TChannelValue>(Stream stream, int width, int height, ReadOnlySpan<TColor> pixels)
where TChannelValue : unmanaged
where TColor : unmanaged, IColor<TChannelValue>
{
int ChannelCount = Color.GetChannelCount<TColor>();
ExrPixelType PixelType = Unsafe.SizeOf<TChannelValue>() <= sizeof(ushort) ? ExrPixelType.Half : ExrPixelType.Single;
int ChannelSize = PixelType is ExrPixelType.Half ? Unsafe.SizeOf<Half>() : sizeof(float);
using BinaryWriter writer = new BinaryWriter(stream);
//Magic bytes
writer.Write(Magic);
//Version bytes
writer.Write((byte)2);//OpenEXR version 2
writer.Write(ExrImageFormat.ScanLines.ToVersionByte());
writer.WriteNullByte();
writer.WriteNullByte();
ExrChannel[] channels;
{
Span<ExrChannel> buffer = [default, default, default, default];
//Stackalloc doesn't work for managed types.
//This can be reworked when ref structs can be used in generics.
int i = 0;
//The order doesn't really matter here because the format sorts channels alphabetically
//when reading reading scan lines, but this makes that more clear.
if (TColor.HasAlphaChannel)
{
buffer[i] = new ExrChannel("A", PixelType, false, 1, 1);
i++;
}
if (TColor.HasBlueChannel)
{
buffer[i] = new ExrChannel("B", PixelType, false, 1, 1);
i++;
}
if (TColor.HasGreenChannel)
{
buffer[i] = new ExrChannel("G", PixelType, false, 1, 1);
i++;
}
if (TColor.HasRedChannel)
{
buffer[i] = new ExrChannel("R", PixelType, false, 1, 1);
i++;
}
channels = buffer[..i].ToArray();
}
//Attributes
writer.WriteAttribute(ExrAttributes.Channels, new ExrChannelList(channels));
writer.WriteAttribute(ExrAttributes.Compression, "compression"u8, ExrCompression.None);
writer.WriteAttribute(ExrAttributes.DataWindow, new ExrBox2I(0, 0, width - 1, height - 1));
writer.WriteAttribute(ExrAttributes.DisplayWindow, new ExrBox2I(0, 0, width - 1, height - 1));
writer.WriteAttribute(ExrAttributes.LineOrder, "lineOrder"u8, ExrLineOrder.IncreasingY);
writer.WriteAttribute(ExrAttributes.PixelAspectRatio, new ExrSingle(1f));
writer.WriteAttribute(ExrAttributes.ScreenWindowCenter, new ExrVector2(0f, 0f));
writer.WriteAttribute(ExrAttributes.ScreenWindowWidth, new ExrSingle(1f));
writer.WriteNullByte();//End of header
//Scan line offset table
long offset = writer.BaseStream.Position + height * sizeof(ulong);
for (int i = 0; i < height; i++)
{
writer.Write(offset);
offset += sizeof(int) + ChannelCount * ChannelSize * width;
}
//Scan lines
if (PixelType is ExrPixelType.Half)
{
for (int i = 0; i < height; i++)
{
writer.Write(i);
writer.Write(ChannelCount * ChannelSize * width);
if (TColor.HasAlphaChannel)
{
for (int j = 0; j < width; j++)
{
Half alpha = NumericConversion.Convert<TChannelValue, Half>(pixels[i * width + j].A);
writer.Write(alpha);
}
}
if (TColor.HasBlueChannel)
{
for (int j = 0; j < width; j++)
{
float blue = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].B);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write((Half)ConvertSRGB(blue * alpha));
}
else
{
writer.Write((Half)ConvertSRGB(blue));
}
}
}
if (TColor.HasGreenChannel)
{
for (int j = 0; j < width; j++)
{
float green = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].G);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write((Half)ConvertSRGB(green * alpha));
}
else
{
writer.Write((Half)ConvertSRGB(green));
}
}
}
if (TColor.HasRedChannel)
{
for (int j = 0; j < width; j++)
{
float red = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].R);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write((Half)ConvertSRGB(red * alpha));
}
else
{
writer.Write((Half)ConvertSRGB(red));
}
}
}
}
}
else
{
for (int i = 0; i < height; i++)
{
writer.Write(i);
writer.Write(ChannelCount * ChannelSize * width);
if (TColor.HasAlphaChannel)
{
for (int j = 0; j < width; j++)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write(alpha);
}
}
if (TColor.HasBlueChannel)
{
for (int j = 0; j < width; j++)
{
float blue = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].B);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write(ConvertSRGB(blue * alpha));
}
else
{
writer.Write(ConvertSRGB(blue));
}
}
}
if (TColor.HasGreenChannel)
{
for (int j = 0; j < width; j++)
{
float green = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].G);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write(ConvertSRGB(green * alpha));
}
else
{
writer.Write(ConvertSRGB(green));
}
}
}
if (TColor.HasRedChannel)
{
for (int j = 0; j < width; j++)
{
float red = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].R);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write(ConvertSRGB(red * alpha));
}
else
{
writer.Write(ConvertSRGB(red));
}
}
}
}
}
}
private static float ConvertSRGB(float value)
{
//https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
//https://en.wikipedia.org/wiki/Gamma_correction#Computer_displays
if (value < 0.04045)
{
return value / 12.92f;
}
else
{
return float.Pow((value + 0.055f) / 1.055f, 2.4f);
}
}
}
@@ -0,0 +1,10 @@
namespace AssetRipper.TextureDecoder.Exr;
public interface IExrDataType
{
static abstract ReadOnlySpan<byte> TypeName { get; }
int Size { get; }
void Write(BinaryWriter writer);
}
@@ -0,0 +1,15 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct TileDescription(int XSize, int YSize, ExrLevelMode Mode) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "tileDesc"u8;
public int Size => sizeof(int) + sizeof(int) + sizeof(ExrLevelMode);
public void Write(BinaryWriter writer)
{
writer.Write(XSize);
writer.Write(YSize);
writer.Write(Mode);
}
}
@@ -33,6 +33,7 @@ namespace AssetRipper.TextureDecoder.Pvrtc
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int DecompressPVRTC(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<byte> output)
{
ThrowHelper.ThrowIfNotLittleEndian();
int xBlockSize = do2bitMode ? BlockX2bpp : BlockX4bpp;
// for MBX don't allow the sizes to get too small
int blockXDim = Math.Max(2, xDim / xBlockSize);
@@ -217,36 +218,36 @@ namespace AssetRipper.TextureDecoder.Pvrtc
int modVal;
if (modulationModes[y * 16 + x] == 0)
{
modVal = m_repVals0[modulationVals[y * 16 + x]];
modVal = RepVals0[modulationVals[y * 16 + x]];
}
else if (do2bitMode)
{
// if this is a stored value
if (((x ^ y) & 1) == 0)
{
modVal = m_repVals0[modulationVals[y * 16 + x]];
modVal = RepVals0[modulationVals[y * 16 + x]];
}
// else average from the neighbours if H&V interpolation..
else if (modulationModes[y * 16 + x] == 1)
{
modVal = (m_repVals0[modulationVals[(y - 1) * 16 + x]] + m_repVals0[modulationVals[(y + 1) * 16 + x]] +
m_repVals0[modulationVals[y * 16 + x - 1]] + m_repVals0[modulationVals[y * 16 + x + 1]] + 2) / 4;
modVal = (RepVals0[modulationVals[(y - 1) * 16 + x]] + RepVals0[modulationVals[(y + 1) * 16 + x]] +
RepVals0[modulationVals[y * 16 + x - 1]] + RepVals0[modulationVals[y * 16 + x + 1]] + 2) / 4;
}
// else if H-Only
else if (modulationModes[y * 16 + x] == 2)
{
modVal = (m_repVals0[modulationVals[y * 16 + x - 1]] + m_repVals0[modulationVals[y * 16 + x + 1]] + 1) / 2;
modVal = (RepVals0[modulationVals[y * 16 + x - 1]] + RepVals0[modulationVals[y * 16 + x + 1]] + 1) / 2;
}
// else it's V-Only
else
{
modVal = (m_repVals0[modulationVals[(y - 1) * 16 + x]] + m_repVals0[modulationVals[(y + 1) * 16 + x]] + 1) / 2;
modVal = (RepVals0[modulationVals[(y - 1) * 16 + x]] + RepVals0[modulationVals[(y + 1) * 16 + x]] + 1) / 2;
}
}
// else it's 4BPP and PT encoding
else
{
modVal = m_repVals1[modulationVals[y * 16 + x]];
modVal = RepVals1[modulationVals[y * 16 + x]];
doPT = modulationVals[y * 16 + x] == PTIndex;
}
@@ -487,7 +488,7 @@ namespace AssetRipper.TextureDecoder.Pvrtc
private const int BlockX2bpp = 8;
private const int BlockX4bpp = 4;
private static readonly int[] m_repVals0 = [0, 3, 5, 8];
private static readonly int[] m_repVals1 = [0, 4, 4, 8];
private static ReadOnlySpan<int> RepVals0 => [0, 3, 5, 8];
private static ReadOnlySpan<int> RepVals1 => [0, 4, 4, 8];
}
}
@@ -0,0 +1,7 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct A : IChannel
{
static bool IChannel.IsAlpha => true;
static T IChannel.GetBlack<T>() => NumericConversion.GetMaximumValueSafe<T>();
}
@@ -0,0 +1,6 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct B : IChannel
{
static bool IChannel.IsBlue => true;
}
@@ -0,0 +1,108 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public static class Channel
{
public static bool TryGetRed<TChannel, TValue>(in TValue field, out TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsRed)
{
value = TChannel.GetRed(field);
return true;
}
else
{
value = default;
return false;
}
}
public static bool TryGetGreen<TChannel, TValue>(in TValue field, out TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsGreen)
{
value = TChannel.GetGreen(field);
return true;
}
else
{
value = default;
return false;
}
}
public static bool TryGetBlue<TChannel, TValue>(in TValue field, out TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsBlue)
{
value = TChannel.GetBlue(field);
return true;
}
else
{
value = default;
return false;
}
}
public static bool TryGetAlpha<TChannel, TValue>(in TValue field, out TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsAlpha)
{
value = TChannel.GetAlpha(field);
return true;
}
else
{
value = default;
return false;
}
}
public static void SetIfRed<TChannel, TValue>(ref TValue field, TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsRed)
{
TChannel.SetRed(ref field, value);
}
}
public static void SetIfGreen<TChannel, TValue>(ref TValue field, TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsGreen)
{
TChannel.SetGreen(ref field, value);
}
}
public static void SetIfBlue<TChannel, TValue>(ref TValue field, TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsBlue)
{
TChannel.SetBlue(ref field, value);
}
}
public static void SetIfAlpha<TChannel, TValue>(ref TValue field, TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsAlpha)
{
TChannel.SetAlpha(ref field, value);
}
}
}
@@ -0,0 +1,6 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct G : IChannel
{
static bool IChannel.IsGreen => true;
}
@@ -0,0 +1,9 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct Gray : IChannel
{
static bool IChannel.IsRed => true;
static bool IChannel.IsGreen => true;
static bool IChannel.IsBlue => true;
static bool IChannel.FullyUtilized => false;
}
@@ -0,0 +1,60 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public interface IChannel
{
static virtual bool IsRed => false;
static virtual bool IsGreen => false;
static virtual bool IsBlue => false;
static virtual bool IsAlpha => false;
static virtual bool FullyUtilized => true;
static virtual T GetBlack<T>() where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return NumericConversion.GetMinimumValueSafe<T>();
}
static virtual T GetWhite<T>() where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return NumericConversion.GetMaximumValueSafe<T>();
}
static virtual T GetRed<T>(T field) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return field;
}
static virtual T GetGreen<T>(T field) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return field;
}
static virtual T GetBlue<T>(T field) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return field;
}
static virtual T GetAlpha<T>(T field) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return field;
}
static virtual void SetRed<T>(ref T field, T value) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
field = value;
}
static virtual void SetGreen<T>(ref T field, T value) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
field = value;
}
static virtual void SetBlue<T>(ref T field, T value) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
field = value;
}
static virtual void SetAlpha<T>(ref T field, T value) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
field = value;
}
}
@@ -0,0 +1,6 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct R : IChannel
{
static bool IChannel.IsRed => true;
}
+7 -7
View File
@@ -74,11 +74,11 @@ public static class Color
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static TTarget Convert<TThis, TThisChannel, TTarget, TTargetChannel>(this TThis color)
where TThisChannel : unmanaged
where TTargetChannel : unmanaged
where TThis : unmanaged, IColor<TThisChannel>
where TTarget : unmanaged, IColor<TTargetChannel>
public static TTarget Convert<TThis, TThisChannelValue, TTarget, TTargetChannelValue>(this TThis color)
where TThisChannelValue : unmanaged
where TTargetChannelValue : unmanaged
where TThis : unmanaged, IColor<TThisChannelValue>
where TTarget : unmanaged, IColor<TTargetChannelValue>
{
if (typeof(TThis) == typeof(TTarget))
{
@@ -87,8 +87,8 @@ public static class Color
else
{
TTarget destination = default;
color.GetChannels(out TThisChannel r, out TThisChannel g, out TThisChannel b, out TThisChannel a);
destination.SetConvertedChannels<TTarget, TTargetChannel, TThisChannel>(r, g, b, a);
color.GetChannels(out TThisChannelValue r, out TThisChannelValue g, out TThisChannelValue b, out TThisChannelValue a);
destination.SetConvertedChannels<TTarget, TTargetChannelValue, TThisChannelValue>(r, g, b, a);
return destination;
}
}
+131
View File
@@ -0,0 +1,131 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Channels;
namespace AssetRipper.TextureDecoder.Rgb;
public partial struct Color<TChannelValue, TChannel> : IColor<Color<TChannelValue, TChannel>, TChannelValue>
where TChannelValue : unmanaged, INumberBase<TChannelValue>, IMinMaxValue<TChannelValue>
where TChannel : IChannel
{
private TChannelValue value;
public TChannelValue R
{
readonly get
{
if (TChannel.IsRed)
{
return TChannel.GetRed(value);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfRed<TChannel, TChannelValue>(ref value, value);
}
}
public TChannelValue G
{
readonly get
{
if (TChannel.IsGreen)
{
return TChannel.GetGreen(value);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfGreen<TChannel, TChannelValue>(ref value, value);
}
}
public TChannelValue B
{
readonly get
{
if (TChannel.IsBlue)
{
return TChannel.GetBlue(value);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfBlue<TChannel, TChannelValue>(ref value, value);
}
}
public TChannelValue A
{
readonly get
{
if (TChannel.IsAlpha)
{
return TChannel.GetAlpha(value);
}
else
{
return NumericConversion.GetMaximumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfAlpha<TChannel, TChannelValue>(ref value, value);
}
}
public Color(TChannelValue value)
{
this.value = value;
}
public readonly void GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(TChannelValue r, TChannelValue g, TChannelValue b, TChannelValue a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => TChannel.IsRed;
static bool IColor.HasGreenChannel => TChannel.IsGreen;
static bool IColor.HasBlueChannel => TChannel.IsBlue;
static bool IColor.HasAlphaChannel => TChannel.IsAlpha;
static bool IColor.ChannelsAreFullyUtilized => TChannel.FullyUtilized;
static Type IColor.ChannelType => typeof(TChannelValue);
public static Color<TChannelValue, TChannel> Black
{
get => new(TChannel.GetBlack<TChannelValue>());
}
public static Color<TChannelValue, TChannel> White
{
get => new(TChannel.GetWhite<TChannelValue>());
}
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
+154
View File
@@ -0,0 +1,154 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Channels;
namespace AssetRipper.TextureDecoder.Rgb;
public partial struct Color<TChannelValue, TChannel1, TChannel2> : IColor<Color<TChannelValue, TChannel1, TChannel2>, TChannelValue>
where TChannelValue : unmanaged, INumberBase<TChannelValue>, IMinMaxValue<TChannelValue>
where TChannel1 : IChannel
where TChannel2 : IChannel
{
private TChannelValue value1;
private TChannelValue value2;
public TChannelValue R
{
readonly get
{
if (TChannel1.IsRed)
{
return TChannel1.GetRed(value1);
}
else if (TChannel2.IsRed)
{
return TChannel2.GetRed(value2);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfRed<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfRed<TChannel2, TChannelValue>(ref value2, value);
}
}
public TChannelValue G
{
readonly get
{
if (TChannel1.IsGreen)
{
return TChannel1.GetGreen(value1);
}
else if (TChannel2.IsGreen)
{
return TChannel2.GetGreen(value2);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfGreen<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfGreen<TChannel2, TChannelValue>(ref value2, value);
}
}
public TChannelValue B
{
readonly get
{
if (TChannel1.IsBlue)
{
return TChannel1.GetBlue(value1);
}
else if (TChannel2.IsBlue)
{
return TChannel2.GetBlue(value2);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfBlue<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfBlue<TChannel2, TChannelValue>(ref value2, value);
}
}
public TChannelValue A
{
readonly get
{
if (TChannel1.IsAlpha)
{
return TChannel1.GetAlpha(value1);
}
else if (TChannel2.IsAlpha)
{
return TChannel2.GetAlpha(value2);
}
else
{
return NumericConversion.GetMaximumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfAlpha<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfAlpha<TChannel2, TChannelValue>(ref value2, value);
}
}
public Color(TChannelValue value1, TChannelValue value2)
{
this.value1 = value1;
this.value2 = value2;
}
public readonly void GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(TChannelValue r, TChannelValue g, TChannelValue b, TChannelValue a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => TChannel1.IsRed || TChannel2.IsRed;
static bool IColor.HasGreenChannel => TChannel1.IsGreen || TChannel2.IsGreen;
static bool IColor.HasBlueChannel => TChannel1.IsBlue || TChannel2.IsBlue;
static bool IColor.HasAlphaChannel => TChannel1.IsAlpha || TChannel2.IsAlpha;
static bool IColor.ChannelsAreFullyUtilized => TChannel1.FullyUtilized && TChannel2.FullyUtilized;
static Type IColor.ChannelType => typeof(TChannelValue);
public static Color<TChannelValue, TChannel1, TChannel2> Black
{
get => new(TChannel1.GetBlack<TChannelValue>(), TChannel2.GetBlack<TChannelValue>());
}
public static Color<TChannelValue, TChannel1, TChannel2> White
{
get => new(TChannel1.GetWhite<TChannelValue>(), TChannel2.GetWhite<TChannelValue>());
}
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
+177
View File
@@ -0,0 +1,177 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Channels;
namespace AssetRipper.TextureDecoder.Rgb;
public partial struct Color<TChannelValue, TChannel1, TChannel2, TChannel3> : IColor<Color<TChannelValue, TChannel1, TChannel2, TChannel3>, TChannelValue>
where TChannelValue : unmanaged, INumberBase<TChannelValue>, IMinMaxValue<TChannelValue>
where TChannel1 : IChannel
where TChannel2 : IChannel
where TChannel3 : IChannel
{
private TChannelValue value1;
private TChannelValue value2;
private TChannelValue value3;
public TChannelValue R
{
readonly get
{
if (TChannel1.IsRed)
{
return TChannel1.GetRed(value1);
}
else if (TChannel2.IsRed)
{
return TChannel2.GetRed(value2);
}
else if (TChannel3.IsRed)
{
return TChannel3.GetRed(value3);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfRed<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfRed<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfRed<TChannel3, TChannelValue>(ref value3, value);
}
}
public TChannelValue G
{
readonly get
{
if (TChannel1.IsGreen)
{
return TChannel1.GetGreen(value1);
}
else if (TChannel2.IsGreen)
{
return TChannel2.GetGreen(value2);
}
else if (TChannel3.IsGreen)
{
return TChannel3.GetGreen(value3);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfGreen<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfGreen<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfGreen<TChannel3, TChannelValue>(ref value3, value);
}
}
public TChannelValue B
{
readonly get
{
if (TChannel1.IsBlue)
{
return TChannel1.GetBlue(value1);
}
else if (TChannel2.IsBlue)
{
return TChannel2.GetBlue(value2);
}
else if (TChannel3.IsBlue)
{
return TChannel3.GetBlue(value3);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfBlue<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfBlue<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfBlue<TChannel3, TChannelValue>(ref value3, value);
}
}
public TChannelValue A
{
readonly get
{
if (TChannel1.IsAlpha)
{
return TChannel1.GetAlpha(value1);
}
else if (TChannel2.IsAlpha)
{
return TChannel2.GetAlpha(value2);
}
else if (TChannel3.IsAlpha)
{
return TChannel3.GetAlpha(value3);
}
else
{
return NumericConversion.GetMaximumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfAlpha<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfAlpha<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfAlpha<TChannel3, TChannelValue>(ref value3, value);
}
}
public Color(TChannelValue value1, TChannelValue value2, TChannelValue value3)
{
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
}
public readonly void GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(TChannelValue r, TChannelValue g, TChannelValue b, TChannelValue a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => TChannel1.IsRed || TChannel2.IsRed || TChannel3.IsRed;
static bool IColor.HasGreenChannel => TChannel1.IsGreen || TChannel2.IsGreen || TChannel3.IsGreen;
static bool IColor.HasBlueChannel => TChannel1.IsBlue || TChannel2.IsBlue || TChannel3.IsBlue;
static bool IColor.HasAlphaChannel => TChannel1.IsAlpha || TChannel2.IsAlpha || TChannel3.IsAlpha;
static bool IColor.ChannelsAreFullyUtilized => TChannel1.FullyUtilized && TChannel2.FullyUtilized && TChannel3.FullyUtilized;
static Type IColor.ChannelType => typeof(TChannelValue);
public static Color<TChannelValue, TChannel1, TChannel2, TChannel3> Black
{
get => new(TChannel1.GetBlack<TChannelValue>(), TChannel2.GetBlack<TChannelValue>(), TChannel3.GetBlack<TChannelValue>());
}
public static Color<TChannelValue, TChannel1, TChannel2, TChannel3> White
{
get => new(TChannel1.GetWhite<TChannelValue>(), TChannel2.GetWhite<TChannelValue>(), TChannel3.GetWhite<TChannelValue>());
}
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
+200
View File
@@ -0,0 +1,200 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Channels;
namespace AssetRipper.TextureDecoder.Rgb;
public partial struct Color<TChannelValue, TChannel1, TChannel2, TChannel3, TChannel4> : IColor<Color<TChannelValue, TChannel1, TChannel2, TChannel3, TChannel4>, TChannelValue>
where TChannelValue : unmanaged, INumberBase<TChannelValue>, IMinMaxValue<TChannelValue>
where TChannel1 : IChannel
where TChannel2 : IChannel
where TChannel3 : IChannel
where TChannel4 : IChannel
{
private TChannelValue value1;
private TChannelValue value2;
private TChannelValue value3;
private TChannelValue value4;
public TChannelValue R
{
readonly get
{
if (TChannel1.IsRed)
{
return TChannel1.GetRed(value1);
}
else if (TChannel2.IsRed)
{
return TChannel2.GetRed(value2);
}
else if (TChannel3.IsRed)
{
return TChannel3.GetRed(value3);
}
else if (TChannel4.IsRed)
{
return TChannel4.GetRed(value4);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfRed<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfRed<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfRed<TChannel3, TChannelValue>(ref value3, value);
Channel.SetIfRed<TChannel4, TChannelValue>(ref value4, value);
}
}
public TChannelValue G
{
readonly get
{
if (TChannel1.IsGreen)
{
return TChannel1.GetGreen(value1);
}
else if (TChannel2.IsGreen)
{
return TChannel2.GetGreen(value2);
}
else if (TChannel3.IsGreen)
{
return TChannel3.GetGreen(value3);
}
else if (TChannel4.IsGreen)
{
return TChannel4.GetGreen(value4);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfGreen<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfGreen<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfGreen<TChannel3, TChannelValue>(ref value3, value);
Channel.SetIfGreen<TChannel4, TChannelValue>(ref value4, value);
}
}
public TChannelValue B
{
readonly get
{
if (TChannel1.IsBlue)
{
return TChannel1.GetBlue(value1);
}
else if (TChannel2.IsBlue)
{
return TChannel2.GetBlue(value2);
}
else if (TChannel3.IsBlue)
{
return TChannel3.GetBlue(value3);
}
else if (TChannel4.IsBlue)
{
return TChannel4.GetBlue(value4);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfBlue<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfBlue<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfBlue<TChannel3, TChannelValue>(ref value3, value);
Channel.SetIfBlue<TChannel4, TChannelValue>(ref value4, value);
}
}
public TChannelValue A
{
readonly get
{
if (TChannel1.IsAlpha)
{
return TChannel1.GetAlpha(value1);
}
else if (TChannel2.IsAlpha)
{
return TChannel2.GetAlpha(value2);
}
else if (TChannel3.IsAlpha)
{
return TChannel3.GetAlpha(value3);
}
else if (TChannel4.IsAlpha)
{
return TChannel4.GetAlpha(value4);
}
else
{
return NumericConversion.GetMaximumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfAlpha<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfAlpha<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfAlpha<TChannel3, TChannelValue>(ref value3, value);
Channel.SetIfAlpha<TChannel4, TChannelValue>(ref value4, value);
}
}
public Color(TChannelValue value1, TChannelValue value2, TChannelValue value3, TChannelValue value4)
{
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
this.value4 = value4;
}
public readonly void GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(TChannelValue r, TChannelValue g, TChannelValue b, TChannelValue a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => TChannel1.IsRed || TChannel2.IsRed || TChannel3.IsRed || TChannel4.IsRed;
static bool IColor.HasGreenChannel => TChannel1.IsGreen || TChannel2.IsGreen || TChannel3.IsGreen || TChannel4.IsGreen;
static bool IColor.HasBlueChannel => TChannel1.IsBlue || TChannel2.IsBlue || TChannel3.IsBlue || TChannel4.IsBlue;
static bool IColor.HasAlphaChannel => TChannel1.IsAlpha || TChannel2.IsAlpha || TChannel3.IsAlpha || TChannel4.IsAlpha;
static bool IColor.ChannelsAreFullyUtilized => TChannel1.FullyUtilized && TChannel2.FullyUtilized && TChannel3.FullyUtilized && TChannel4.FullyUtilized;
static Type IColor.ChannelType => typeof(TChannelValue);
public static Color<TChannelValue, TChannel1, TChannel2, TChannel3, TChannel4> Black
{
get => new(TChannel1.GetBlack<TChannelValue>(), TChannel2.GetBlack<TChannelValue>(), TChannel3.GetBlack<TChannelValue>(), TChannel4.GetBlack<TChannelValue>());
}
public static Color<TChannelValue, TChannel1, TChannel2, TChannel3, TChannel4> White
{
get => new(TChannel1.GetWhite<TChannelValue>(), TChannel2.GetWhite<TChannelValue>(), TChannel3.GetWhite<TChannelValue>(), TChannel4.GetWhite<TChannelValue>());
}
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
+4 -4
View File
@@ -10,7 +10,7 @@
/// <typeparam name="TSelf">
/// The implementing type.
/// </typeparam>
/// <typeparam name="TChannel">
/// <typeparam name="TChannelValue">
/// Supported types are:
/// <list type="bullet">
/// <item><see cref="sbyte"/></item>
@@ -32,9 +32,9 @@
/// <item><see cref="decimal"/></item>
/// </list>
/// </typeparam>
public interface IColor<TSelf, TChannel> : IColor<TChannel>
where TSelf : unmanaged, IColor<TSelf, TChannel>
where TChannel : unmanaged
public interface IColor<TSelf, TChannelValue> : IColor<TChannelValue>
where TSelf : unmanaged, IColor<TSelf, TChannelValue>
where TChannelValue : unmanaged
{
/// <summary>
/// A black pixel.
@@ -503,6 +503,7 @@ namespace AssetRipper.TextureDecoder.Rgb
where TDestinationChannel : unmanaged
where TDestinationColor : unmanaged, IColor<TDestinationChannel>
{
ThrowHelper.ThrowIfNotLittleEndian();
ReadOnlySpan<TSourceColor> sourceSpan = MemoryMarshal.Cast<byte, TSourceColor>(input).Slice(0, width * height);
Span<TDestinationColor> destinationSpan = MemoryMarshal.Cast<byte, TDestinationColor>(output).Slice(0, width * height);
Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>(sourceSpan, destinationSpan);
@@ -19,5 +19,13 @@
throw new InvalidOperationException($"Not enough space. {spaceRequired} is required, but only {spaceAvailable} is available");
}
}
internal static void ThrowIfNotLittleEndian()
{
if (!BitConverter.IsLittleEndian)
{
throw new PlatformNotSupportedException();
}
}
}
}
+17 -16
View File
@@ -35,50 +35,51 @@ namespace AssetRipper.TextureDecoder.Yuy2
/// Decompress a YUY2 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressYUY2<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressYUY2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
output = new byte[width * height * Unsafe.SizeOf<TOutputColor>()];
return DecompressYUY2<TOutputColor, TOutputChannel>(input, width, height, output);
return DecompressYUY2<TOutputColor, TOutputChannelValue>(input, width, height, output);
}
/// <summary>
/// Decompress a YUY2 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height * pixelSize.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressYUY2<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressYUY2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
return DecompressYUY2<TOutputColor, TOutputChannel>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
ThrowHelper.ThrowIfNotLittleEndian();
return DecompressYUY2<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
}
/// <summary>
/// Decompress a YUY2 image
/// </summary>
/// <typeparam name="TOutputColor">The <see cref="IColor{T}"/> type used for each pixel.</typeparam>
/// <typeparam name="TOutputChannel">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <typeparam name="TOutputChannelValue">The channel type used in <typeparamref name="TOutputColor"/>.</typeparam>
/// <param name="input">Input buffer containing the compressed image.</param>
/// <param name="width">Pixel width of the image.</param>
/// <param name="height">Pixel height of the image.</param>
/// <param name="output">An output buffer. Must be at least width * height.</param>
/// <returns>Number of bytes read from <paramref name="input"/></returns>
public static int DecompressYUY2<TOutputColor, TOutputChannel>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannel : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannel>
public static int DecompressYUY2<TOutputColor, TOutputChannelValue>(ReadOnlySpan<byte> input, int width, int height, Span<TOutputColor> output)
where TOutputChannelValue : unmanaged
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
{
ThrowHelper.ThrowIfNotEnoughSpace(output.Length, width * height);
@@ -99,12 +100,12 @@ namespace AssetRipper.TextureDecoder.Yuy2
byte b0 = ClampByte((298 * c + 516 * d + 128) >> 8); // blue
byte g0 = ClampByte((298 * c - 100 * d - 208 * e + 128) >> 8); // green
byte r0 = ClampByte((298 * c + 409 * e + 128) >> 8); // red
output[o++].SetConvertedChannels<TOutputColor, TOutputChannel, byte>(r0, g0, b0);
output[o++].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>(r0, g0, b0);
c = y1 - 16;
byte b1 = ClampByte((298 * c + 516 * d + 128) >> 8); // blue
byte g1 = ClampByte((298 * c - 100 * d - 208 * e + 128) >> 8); // green
byte r1 = ClampByte((298 * c + 409 * e + 128) >> 8); // red
output[o++].SetConvertedChannels<TOutputColor, TOutputChannel, byte>(r1, g1, b1);
output[o++].SetConvertedChannels<TOutputColor, TOutputChannelValue, byte>(r1, g1, b1);
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<Version>2.1.0</Version>
<Version>2.3.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<Copyright>Copyright © ds5678</Copyright>
<Authors>ds5678</Authors>