mirror of
https://github.com/AssetRipper/AssetRipper.TextureDecoder.git
synced 2026-11-12 18:07:28 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19b2469595 | ||
|
|
c5a2382733 | ||
|
|
9ce8311e1b | ||
|
|
e39b439ccd | ||
|
|
f2da43bcbb | ||
|
|
128eb58864 | ||
|
|
9f7a61172a | ||
|
|
93c177232f | ||
|
|
8b62957cdb | ||
|
|
95ecf5b1ac | ||
|
|
f8d17ad290 | ||
|
|
f2772dcb83 | ||
|
|
1e363e355e | ||
|
|
8e82e702a7 | ||
|
|
1957784de4 | ||
|
|
1bf5f70ead | ||
|
|
2bb946b26f | ||
|
|
eed528cc68 | ||
|
|
c6d3236770 | ||
|
|
34d8310764 |
No files matched your search
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/[email protected].0
|
||||
uses: actions/[email protected].1
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore dependencies
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.11" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
</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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.10.0" />
|
||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.2.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.2">
|
||||
<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 _));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -34,14 +34,10 @@ internal static class BcHelpers
|
||||
|
||||
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch, 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];
|
||||
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 +48,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))
|
||||
{
|
||||
@@ -477,7 +473,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 +483,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 +516,7 @@ internal static class BcHelpers
|
||||
}
|
||||
partitionSet &= 0x01;
|
||||
|
||||
int index = bstream.ReadBits(indexBits);
|
||||
int index = bstream.ReadBits(indexBits).ToSigned();
|
||||
|
||||
int ep_i = (partitionSet * 2);
|
||||
|
||||
@@ -542,12 +538,12 @@ internal static class BcHelpers
|
||||
decompressedOffset += destinationPitch * 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
|
||||
@@ -559,12 +555,8 @@ internal static class BcHelpers
|
||||
|
||||
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
{
|
||||
BitStream bstream = new()
|
||||
{
|
||||
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock),
|
||||
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong)))
|
||||
};
|
||||
Span2D<int> endpoints = new(stackalloc int[6 * 4], 6, 4);
|
||||
BitStream bstream = new(compressedBlock);
|
||||
Span2D<uint> endpoints = new(stackalloc uint[6 * 4], 6, 4);
|
||||
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
|
||||
|
||||
int decompressedOffset = 0;
|
||||
@@ -589,29 +581,36 @@ internal static class BcHelpers
|
||||
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 +645,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 +662,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 +704,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 +740,7 @@ internal static class BcHelpers
|
||||
indexBits--;
|
||||
}
|
||||
|
||||
indices[i, j] = bstream.ReadBits(indexBits);
|
||||
indices[i, j] = bstream.ReadBits(indexBits).ToSigned();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,10 +756,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 +770,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
|
||||
@@ -811,12 +825,13 @@ internal static class BcHelpers
|
||||
|
||||
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 +964,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 +981,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 +1053,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 +1076,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,7 +1087,12 @@ 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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -36,50 +36,50 @@ 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));
|
||||
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 +97,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 +157,50 @@ 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));
|
||||
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 +244,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 +288,50 @@ 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));
|
||||
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 +390,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;
|
||||
|
||||
@@ -805,7 +805,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 +849,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 +867,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 +878,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 +898,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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -217,36 +217,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 +487,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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} }}";
|
||||
}
|
||||
}
|
||||
@@ -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} }}";
|
||||
}
|
||||
}
|
||||
@@ -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} }}";
|
||||
}
|
||||
}
|
||||
@@ -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} }}";
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -35,50 +35,50 @@ 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));
|
||||
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 +99,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||
|
||||
<Version>2.1.0</Version>
|
||||
<Version>2.2.1</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<Copyright>Copyright © ds5678</Copyright>
|
||||
<Authors>ds5678</Authors>
|
||||
|
||||
Reference in new issue
Block a user