Compare commits

..
6 Commits
Author SHA1 Message Date
Jeremy Pritts f2772dcb83 Bump version 2024-04-01 21:33:33 -07:00
Jeremy Pritts 1e363e355e Update NuGet references 2024-03-24 17:03:38 -07:00
Jeremy Pritts 8e82e702a7 OpenEXR writing 2024-03-24 17:00:42 -07:00
Jeremy Pritts 1957784de4 Support specifying channels 2024-03-24 16:59:44 -07:00
Jeremy Pritts 1bf5f70ead Rename Channel to ChannelValue 2024-03-24 16:54:23 -07:00
dependabot[bot] 2bb946b26f Bump BenchmarkDotNet from 0.13.11 to 0.13.12
Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.11 to 0.13.12.
- [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases)
- [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.11...v0.13.12)

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

Signed-off-by: dependabot[bot] <[email protected]>
2024-01-07 21:18:06 -05:00
47 changed files with 1866 additions and 178 deletions

No files matched your search

@@ -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="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.10.0">
<PackageReference Include="NUnit.Analyzers" Version="4.1.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>
@@ -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>
+50 -50
View File
@@ -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;
@@ -0,0 +1,112 @@
using System.Buffers;
using System.Text;
namespace AssetRipper.TextureDecoder.Exr;
internal static class BinaryWriterExtensions
{
public static void WriteLengthPrefixedString(this BinaryWriter writer, string value)
{
int length = Encoding.UTF8.GetByteCount(value);
writer.Write(length);
byte[]? rentedArray;
scoped Span<byte> buffer;
if (length <= 1024)
{
rentedArray = null;
buffer = stackalloc byte[length];
}
else
{
rentedArray = ArrayPool<byte>.Shared.Rent(length);
buffer = rentedArray.AsSpan(0, length);
}
Encoding.UTF8.GetBytes(value, buffer);
writer.Write(buffer);
if (rentedArray is not null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
}
public static void WriteNullTerminatedString(this BinaryWriter writer, string value)
{
int length = Encoding.UTF8.GetByteCount(value);
byte[]? rentedArray;
scoped Span<byte> buffer;
if (length <= 1024)
{
rentedArray = null;
buffer = stackalloc byte[length];
}
else
{
rentedArray = ArrayPool<byte>.Shared.Rent(length);
buffer = rentedArray.AsSpan(0, length);
}
Encoding.UTF8.GetBytes(value, buffer);
writer.Write(buffer);
if (rentedArray is not null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
writer.WriteNullByte();
}
public static void WriteNullTerminatedString(this BinaryWriter writer, ReadOnlySpan<byte> utf8String)
{
writer.Write(utf8String);
writer.WriteNullByte();
}
public static void WriteNullByte(this BinaryWriter writer)
{
writer.Write(default(byte));
}
public static void Write<T>(this BinaryWriter writer, T value) where T : unmanaged, Enum
{
switch (Unsafe.SizeOf<T>())
{
case sizeof(byte):
writer.Write(Unsafe.As<T, byte>(ref value));
break;
case sizeof(ushort):
writer.Write(Unsafe.As<T, ushort>(ref value));
break;
case sizeof(uint):
writer.Write(Unsafe.As<T, uint>(ref value));
break;
case sizeof(ulong):
writer.Write(Unsafe.As<T, ulong>(ref value));
break;
}
}
public static void WriteAttribute<T>(this BinaryWriter writer, ReadOnlySpan<byte> attributeName, T attributeValue) where T : IExrDataType
{
writer.Write(attributeName);
writer.WriteNullByte();
writer.Write(T.TypeName);
writer.WriteNullByte();
writer.Write(attributeValue.Size);
attributeValue.Write(writer);
}
public static void WriteAttribute<T>(this BinaryWriter writer, ReadOnlySpan<byte> attributeName, ReadOnlySpan<byte> attributeType, T attributeValue) where T : unmanaged, Enum
{
writer.Write(attributeName);
writer.WriteNullByte();
writer.Write(attributeType);
writer.WriteNullByte();
writer.Write(Unsafe.SizeOf<T>());
writer.Write(attributeValue);
}
}
@@ -0,0 +1,22 @@
namespace AssetRipper.TextureDecoder.Exr;
public static class ExrAttributes
{
public static ReadOnlySpan<byte> Channels => "channels"u8;
public static ReadOnlySpan<byte> Compression => "compression"u8;
public static ReadOnlySpan<byte> DataWindow => "dataWindow"u8;
public static ReadOnlySpan<byte> DisplayWindow => "displayWindow"u8;
public static ReadOnlySpan<byte> LineOrder => "lineOrder"u8;
public static ReadOnlySpan<byte> PixelAspectRatio => "pixelAspectRatio"u8;
public static ReadOnlySpan<byte> ScreenWindowCenter => "screenWindowCenter"u8;
public static ReadOnlySpan<byte> ScreenWindowWidth => "screenWindowWidth"u8;
public static ReadOnlySpan<byte> Tiles => "tiles"u8;
}
@@ -0,0 +1,16 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrBox2F(float XMin, float YMin, float XMax, float YMax) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "box2f"u8;
public int Size => sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float);
public void Write(BinaryWriter writer)
{
writer.Write(XMin);
writer.Write(YMin);
writer.Write(XMax);
writer.Write(YMax);
}
}
@@ -0,0 +1,16 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrBox2I(int XMin, int YMin, int XMax, int YMax) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "box2i"u8;
public int Size => sizeof(int) + sizeof(int) + sizeof(int) + sizeof(int);
public void Write(BinaryWriter writer)
{
writer.Write(XMin);
writer.Write(YMin);
writer.Write(XMax);
writer.Write(YMax);
}
}
@@ -0,0 +1,32 @@
using System.Text;
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrChannel(string Name, ExrPixelType PixelType, bool PLinear, int XSampling, int YSampling)
{
public int Size
{
get
{
return Encoding.UTF8.GetByteCount(Name)
+ sizeof(byte)//Null terminator
+ sizeof(ExrPixelType)
+ sizeof(bool)
+ 3 * sizeof(byte)//Reserved bytes
+ sizeof(int)
+ sizeof(int);
}
}
public void Write(BinaryWriter writer)
{
writer.WriteNullTerminatedString(Name);
writer.Write(PixelType);
writer.Write(PLinear);
writer.WriteNullByte();
writer.WriteNullByte();
writer.WriteNullByte();
writer.Write(XSampling);
writer.Write(YSampling);
}
}
@@ -0,0 +1,17 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrChannelList(ExrChannel[] Channels) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "chlist"u8;
public int Size => Channels.Sum(c => c.Size);
public void Write(BinaryWriter writer)
{
foreach (ExrChannel channel in Channels)
{
channel.Write(writer);
}
writer.WriteNullByte();
}
}
@@ -0,0 +1,13 @@
namespace AssetRipper.TextureDecoder.Exr;
public enum ExrCompression : byte
{
None = 0,
RLE = 1,
ZIPS = 2,
ZIP = 3,
PIZ = 4,
PXR24 = 5,
B44 = 6,
B44A = 7,
}
@@ -0,0 +1,17 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrDouble(double Value) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "double"u8;
public int Size => sizeof(double);
public void Write(BinaryWriter writer)
{
writer.Write(Value);
}
public static implicit operator double(ExrDouble value) => value.Value;
public static implicit operator ExrDouble(double value) => new(value);
}
@@ -0,0 +1,14 @@
namespace AssetRipper.TextureDecoder.Exr;
public enum ExrImageFormat
{
ScanLines,
Tiles,
}
public static class ExrImageFormatExtensions
{
public static byte ToVersionByte(this ExrImageFormat format) => format switch
{
ExrImageFormat.Tiles => 0x02,
_ => 0x00,
};
}
@@ -0,0 +1,17 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrInt32(int Value) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "int"u8;
public int Size => sizeof(int);
public void Write(BinaryWriter writer)
{
writer.Write(Value);
}
public static implicit operator int(ExrInt32 value) => value.Value;
public static implicit operator ExrInt32(int value) => new(value);
}
@@ -0,0 +1,11 @@
namespace AssetRipper.TextureDecoder.Exr;
[Flags]
public enum ExrLevelMode : byte
{
OneLevel = 0,
MipMapLevels = 1,
RipMapLevels = 2,
RoundDown = 0,
RoundUp = 16,
}
@@ -0,0 +1,8 @@
namespace AssetRipper.TextureDecoder.Exr;
public enum ExrLineOrder : byte
{
IncreasingY = 0,
DecreasingY = 1,
RandomY = 2,
}
@@ -0,0 +1,8 @@
namespace AssetRipper.TextureDecoder.Exr;
public enum ExrPixelType : int
{
UInt = 0,
Half = 1,
Single = 2,
}
@@ -0,0 +1,17 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrSingle(float Value) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "float"u8;
public int Size => sizeof(float);
public void Write(BinaryWriter writer)
{
writer.Write(Value);
}
public static implicit operator float(ExrSingle value) => value.Value;
public static implicit operator ExrSingle(float value) => new(value);
}
@@ -0,0 +1,16 @@
using System.Text;
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrString(string Value) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "string"u8;
public int Size => sizeof(int) + Encoding.UTF8.GetByteCount(Value);
public void Write(BinaryWriter writer) => writer.WriteLengthPrefixedString(Value);
public static implicit operator string(ExrString value) => value.Value;
public static implicit operator ExrString(string value) => new(value);
}
@@ -0,0 +1,26 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct ExrVector2(Vector2 Value) : IExrDataType
{
public ExrVector2(float x, float y) : this(new Vector2(x, y))
{
}
public int Size => sizeof(float) + sizeof(float);
public float X => Value.X;
public float Y => Value.Y;
public static ReadOnlySpan<byte> TypeName => "v2f"u8;
public void Write(BinaryWriter writer)
{
writer.Write(X);
writer.Write(Y);
}
public static implicit operator Vector2(ExrVector2 value) => value.Value;
public static implicit operator ExrVector2(Vector2 value) => new(value);
}
+225
View File
@@ -0,0 +1,225 @@
using AssetRipper.TextureDecoder.Rgb;
namespace AssetRipper.TextureDecoder.Exr;
public static class ExrWriter
{
public static ReadOnlySpan<byte> Magic => [0x76, 0x2f, 0x31, 0x01];
public static void Write<TColor, TChannelValue>(Stream stream, int width, int height, ReadOnlySpan<TColor> pixels)
where TChannelValue : unmanaged
where TColor : unmanaged, IColor<TChannelValue>
{
int ChannelCount = Color.GetChannelCount<TColor>();
ExrPixelType PixelType = Unsafe.SizeOf<TChannelValue>() <= sizeof(ushort) ? ExrPixelType.Half : ExrPixelType.Single;
int ChannelSize = PixelType is ExrPixelType.Half ? Unsafe.SizeOf<Half>() : sizeof(float);
using BinaryWriter writer = new BinaryWriter(stream);
//Magic bytes
writer.Write(Magic);
//Version bytes
writer.Write((byte)2);//OpenEXR version 2
writer.Write(ExrImageFormat.ScanLines.ToVersionByte());
writer.WriteNullByte();
writer.WriteNullByte();
ExrChannel[] channels;
{
Span<ExrChannel> buffer = [default, default, default, default];
//Stackalloc doesn't work for managed types.
//This can be reworked when ref structs can be used in generics.
int i = 0;
//The order doesn't really matter here because the format sorts channels alphabetically
//when reading reading scan lines, but this makes that more clear.
if (TColor.HasAlphaChannel)
{
buffer[i] = new ExrChannel("A", PixelType, false, 1, 1);
i++;
}
if (TColor.HasBlueChannel)
{
buffer[i] = new ExrChannel("B", PixelType, false, 1, 1);
i++;
}
if (TColor.HasGreenChannel)
{
buffer[i] = new ExrChannel("G", PixelType, false, 1, 1);
i++;
}
if (TColor.HasRedChannel)
{
buffer[i] = new ExrChannel("R", PixelType, false, 1, 1);
i++;
}
channels = buffer[..i].ToArray();
}
//Attributes
writer.WriteAttribute(ExrAttributes.Channels, new ExrChannelList(channels));
writer.WriteAttribute(ExrAttributes.Compression, "compression"u8, ExrCompression.None);
writer.WriteAttribute(ExrAttributes.DataWindow, new ExrBox2I(0, 0, width - 1, height - 1));
writer.WriteAttribute(ExrAttributes.DisplayWindow, new ExrBox2I(0, 0, width - 1, height - 1));
writer.WriteAttribute(ExrAttributes.LineOrder, "lineOrder"u8, ExrLineOrder.IncreasingY);
writer.WriteAttribute(ExrAttributes.PixelAspectRatio, new ExrSingle(1f));
writer.WriteAttribute(ExrAttributes.ScreenWindowCenter, new ExrVector2(0f, 0f));
writer.WriteAttribute(ExrAttributes.ScreenWindowWidth, new ExrSingle(1f));
writer.WriteNullByte();//End of header
//Scan line offset table
long offset = writer.BaseStream.Position + height * sizeof(ulong);
for (int i = 0; i < height; i++)
{
writer.Write(offset);
offset += sizeof(int) + ChannelCount * ChannelSize * width;
}
//Scan lines
if (PixelType is ExrPixelType.Half)
{
for (int i = 0; i < height; i++)
{
writer.Write(i);
writer.Write(ChannelCount * ChannelSize * width);
if (TColor.HasAlphaChannel)
{
for (int j = 0; j < width; j++)
{
Half alpha = NumericConversion.Convert<TChannelValue, Half>(pixels[i * width + j].A);
writer.Write(alpha);
}
}
if (TColor.HasBlueChannel)
{
for (int j = 0; j < width; j++)
{
float blue = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].B);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write((Half)ConvertSRGB(blue * alpha));
}
else
{
writer.Write((Half)ConvertSRGB(blue));
}
}
}
if (TColor.HasGreenChannel)
{
for (int j = 0; j < width; j++)
{
float green = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].G);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write((Half)ConvertSRGB(green * alpha));
}
else
{
writer.Write((Half)ConvertSRGB(green));
}
}
}
if (TColor.HasRedChannel)
{
for (int j = 0; j < width; j++)
{
float red = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].R);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write((Half)ConvertSRGB(red * alpha));
}
else
{
writer.Write((Half)ConvertSRGB(red));
}
}
}
}
}
else
{
for (int i = 0; i < height; i++)
{
writer.Write(i);
writer.Write(ChannelCount * ChannelSize * width);
if (TColor.HasAlphaChannel)
{
for (int j = 0; j < width; j++)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write(alpha);
}
}
if (TColor.HasBlueChannel)
{
for (int j = 0; j < width; j++)
{
float blue = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].B);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write(ConvertSRGB(blue * alpha));
}
else
{
writer.Write(ConvertSRGB(blue));
}
}
}
if (TColor.HasGreenChannel)
{
for (int j = 0; j < width; j++)
{
float green = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].G);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write(ConvertSRGB(green * alpha));
}
else
{
writer.Write(ConvertSRGB(green));
}
}
}
if (TColor.HasRedChannel)
{
for (int j = 0; j < width; j++)
{
float red = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].R);
if (TColor.HasAlphaChannel)
{
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
writer.Write(ConvertSRGB(red * alpha));
}
else
{
writer.Write(ConvertSRGB(red));
}
}
}
}
}
}
private static float ConvertSRGB(float value)
{
//https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
//https://en.wikipedia.org/wiki/Gamma_correction#Computer_displays
if (value < 0.04045)
{
return value / 12.92f;
}
else
{
return float.Pow((value + 0.055f) / 1.055f, 2.4f);
}
}
}
@@ -0,0 +1,10 @@
namespace AssetRipper.TextureDecoder.Exr;
public interface IExrDataType
{
static abstract ReadOnlySpan<byte> TypeName { get; }
int Size { get; }
void Write(BinaryWriter writer);
}
@@ -0,0 +1,15 @@
namespace AssetRipper.TextureDecoder.Exr;
public readonly record struct TileDescription(int XSize, int YSize, ExrLevelMode Mode) : IExrDataType
{
public static ReadOnlySpan<byte> TypeName => "tileDesc"u8;
public int Size => sizeof(int) + sizeof(int) + sizeof(ExrLevelMode);
public void Write(BinaryWriter writer)
{
writer.Write(XSize);
writer.Write(YSize);
writer.Write(Mode);
}
}
@@ -0,0 +1,7 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct A : IChannel
{
static bool IChannel.IsAlpha => true;
static T IChannel.GetBlack<T>() => NumericConversion.GetMaximumValueSafe<T>();
}
@@ -0,0 +1,6 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct B : IChannel
{
static bool IChannel.IsBlue => true;
}
@@ -0,0 +1,108 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public static class Channel
{
public static bool TryGetRed<TChannel, TValue>(in TValue field, out TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsRed)
{
value = TChannel.GetRed(field);
return true;
}
else
{
value = default;
return false;
}
}
public static bool TryGetGreen<TChannel, TValue>(in TValue field, out TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsGreen)
{
value = TChannel.GetGreen(field);
return true;
}
else
{
value = default;
return false;
}
}
public static bool TryGetBlue<TChannel, TValue>(in TValue field, out TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsBlue)
{
value = TChannel.GetBlue(field);
return true;
}
else
{
value = default;
return false;
}
}
public static bool TryGetAlpha<TChannel, TValue>(in TValue field, out TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsAlpha)
{
value = TChannel.GetAlpha(field);
return true;
}
else
{
value = default;
return false;
}
}
public static void SetIfRed<TChannel, TValue>(ref TValue field, TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsRed)
{
TChannel.SetRed(ref field, value);
}
}
public static void SetIfGreen<TChannel, TValue>(ref TValue field, TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsGreen)
{
TChannel.SetGreen(ref field, value);
}
}
public static void SetIfBlue<TChannel, TValue>(ref TValue field, TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsBlue)
{
TChannel.SetBlue(ref field, value);
}
}
public static void SetIfAlpha<TChannel, TValue>(ref TValue field, TValue value)
where TChannel : IChannel
where TValue : unmanaged, INumberBase<TValue>, IMinMaxValue<TValue>
{
if (TChannel.IsAlpha)
{
TChannel.SetAlpha(ref field, value);
}
}
}
@@ -0,0 +1,6 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct G : IChannel
{
static bool IChannel.IsGreen => true;
}
@@ -0,0 +1,9 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct Gray : IChannel
{
static bool IChannel.IsRed => true;
static bool IChannel.IsGreen => true;
static bool IChannel.IsBlue => true;
static bool IChannel.FullyUtilized => false;
}
@@ -0,0 +1,60 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public interface IChannel
{
static virtual bool IsRed => false;
static virtual bool IsGreen => false;
static virtual bool IsBlue => false;
static virtual bool IsAlpha => false;
static virtual bool FullyUtilized => true;
static virtual T GetBlack<T>() where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return NumericConversion.GetMinimumValueSafe<T>();
}
static virtual T GetWhite<T>() where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return NumericConversion.GetMaximumValueSafe<T>();
}
static virtual T GetRed<T>(T field) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return field;
}
static virtual T GetGreen<T>(T field) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return field;
}
static virtual T GetBlue<T>(T field) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return field;
}
static virtual T GetAlpha<T>(T field) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
return field;
}
static virtual void SetRed<T>(ref T field, T value) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
field = value;
}
static virtual void SetGreen<T>(ref T field, T value) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
field = value;
}
static virtual void SetBlue<T>(ref T field, T value) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
field = value;
}
static virtual void SetAlpha<T>(ref T field, T value) where T : unmanaged, INumberBase<T>, IMinMaxValue<T>
{
field = value;
}
}
@@ -0,0 +1,6 @@
namespace AssetRipper.TextureDecoder.Rgb.Channels;
public readonly struct R : IChannel
{
static bool IChannel.IsRed => true;
}
+7 -7
View File
@@ -74,11 +74,11 @@ public static class Color
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static TTarget Convert<TThis, TThisChannel, TTarget, TTargetChannel>(this TThis color)
where TThisChannel : unmanaged
where TTargetChannel : unmanaged
where TThis : unmanaged, IColor<TThisChannel>
where TTarget : unmanaged, IColor<TTargetChannel>
public static TTarget Convert<TThis, TThisChannelValue, TTarget, TTargetChannelValue>(this TThis color)
where TThisChannelValue : unmanaged
where TTargetChannelValue : unmanaged
where TThis : unmanaged, IColor<TThisChannelValue>
where TTarget : unmanaged, IColor<TTargetChannelValue>
{
if (typeof(TThis) == typeof(TTarget))
{
@@ -87,8 +87,8 @@ public static class Color
else
{
TTarget destination = default;
color.GetChannels(out TThisChannel r, out TThisChannel g, out TThisChannel b, out TThisChannel a);
destination.SetConvertedChannels<TTarget, TTargetChannel, TThisChannel>(r, g, b, a);
color.GetChannels(out TThisChannelValue r, out TThisChannelValue g, out TThisChannelValue b, out TThisChannelValue a);
destination.SetConvertedChannels<TTarget, TTargetChannelValue, TThisChannelValue>(r, g, b, a);
return destination;
}
}
+131
View File
@@ -0,0 +1,131 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Channels;
namespace AssetRipper.TextureDecoder.Rgb;
public partial struct Color<TChannelValue, TChannel> : IColor<Color<TChannelValue, TChannel>, TChannelValue>
where TChannelValue : unmanaged, INumberBase<TChannelValue>, IMinMaxValue<TChannelValue>
where TChannel : IChannel
{
private TChannelValue value;
public TChannelValue R
{
readonly get
{
if (TChannel.IsRed)
{
return TChannel.GetRed(value);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfRed<TChannel, TChannelValue>(ref value, value);
}
}
public TChannelValue G
{
readonly get
{
if (TChannel.IsGreen)
{
return TChannel.GetGreen(value);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfGreen<TChannel, TChannelValue>(ref value, value);
}
}
public TChannelValue B
{
readonly get
{
if (TChannel.IsBlue)
{
return TChannel.GetBlue(value);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfBlue<TChannel, TChannelValue>(ref value, value);
}
}
public TChannelValue A
{
readonly get
{
if (TChannel.IsAlpha)
{
return TChannel.GetAlpha(value);
}
else
{
return NumericConversion.GetMaximumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfAlpha<TChannel, TChannelValue>(ref value, value);
}
}
public Color(TChannelValue value)
{
this.value = value;
}
public readonly void GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(TChannelValue r, TChannelValue g, TChannelValue b, TChannelValue a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => TChannel.IsRed;
static bool IColor.HasGreenChannel => TChannel.IsGreen;
static bool IColor.HasBlueChannel => TChannel.IsBlue;
static bool IColor.HasAlphaChannel => TChannel.IsAlpha;
static bool IColor.ChannelsAreFullyUtilized => TChannel.FullyUtilized;
static Type IColor.ChannelType => typeof(TChannelValue);
public static Color<TChannelValue, TChannel> Black
{
get => new(TChannel.GetBlack<TChannelValue>());
}
public static Color<TChannelValue, TChannel> White
{
get => new(TChannel.GetWhite<TChannelValue>());
}
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
+154
View File
@@ -0,0 +1,154 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Channels;
namespace AssetRipper.TextureDecoder.Rgb;
public partial struct Color<TChannelValue, TChannel1, TChannel2> : IColor<Color<TChannelValue, TChannel1, TChannel2>, TChannelValue>
where TChannelValue : unmanaged, INumberBase<TChannelValue>, IMinMaxValue<TChannelValue>
where TChannel1 : IChannel
where TChannel2 : IChannel
{
private TChannelValue value1;
private TChannelValue value2;
public TChannelValue R
{
readonly get
{
if (TChannel1.IsRed)
{
return TChannel1.GetRed(value1);
}
else if (TChannel2.IsRed)
{
return TChannel2.GetRed(value2);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfRed<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfRed<TChannel2, TChannelValue>(ref value2, value);
}
}
public TChannelValue G
{
readonly get
{
if (TChannel1.IsGreen)
{
return TChannel1.GetGreen(value1);
}
else if (TChannel2.IsGreen)
{
return TChannel2.GetGreen(value2);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfGreen<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfGreen<TChannel2, TChannelValue>(ref value2, value);
}
}
public TChannelValue B
{
readonly get
{
if (TChannel1.IsBlue)
{
return TChannel1.GetBlue(value1);
}
else if (TChannel2.IsBlue)
{
return TChannel2.GetBlue(value2);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfBlue<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfBlue<TChannel2, TChannelValue>(ref value2, value);
}
}
public TChannelValue A
{
readonly get
{
if (TChannel1.IsAlpha)
{
return TChannel1.GetAlpha(value1);
}
else if (TChannel2.IsAlpha)
{
return TChannel2.GetAlpha(value2);
}
else
{
return NumericConversion.GetMaximumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfAlpha<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfAlpha<TChannel2, TChannelValue>(ref value2, value);
}
}
public Color(TChannelValue value1, TChannelValue value2)
{
this.value1 = value1;
this.value2 = value2;
}
public readonly void GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(TChannelValue r, TChannelValue g, TChannelValue b, TChannelValue a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => TChannel1.IsRed || TChannel2.IsRed;
static bool IColor.HasGreenChannel => TChannel1.IsGreen || TChannel2.IsGreen;
static bool IColor.HasBlueChannel => TChannel1.IsBlue || TChannel2.IsBlue;
static bool IColor.HasAlphaChannel => TChannel1.IsAlpha || TChannel2.IsAlpha;
static bool IColor.ChannelsAreFullyUtilized => TChannel1.FullyUtilized && TChannel2.FullyUtilized;
static Type IColor.ChannelType => typeof(TChannelValue);
public static Color<TChannelValue, TChannel1, TChannel2> Black
{
get => new(TChannel1.GetBlack<TChannelValue>(), TChannel2.GetBlack<TChannelValue>());
}
public static Color<TChannelValue, TChannel1, TChannel2> White
{
get => new(TChannel1.GetWhite<TChannelValue>(), TChannel2.GetWhite<TChannelValue>());
}
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
+177
View File
@@ -0,0 +1,177 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Channels;
namespace AssetRipper.TextureDecoder.Rgb;
public partial struct Color<TChannelValue, TChannel1, TChannel2, TChannel3> : IColor<Color<TChannelValue, TChannel1, TChannel2, TChannel3>, TChannelValue>
where TChannelValue : unmanaged, INumberBase<TChannelValue>, IMinMaxValue<TChannelValue>
where TChannel1 : IChannel
where TChannel2 : IChannel
where TChannel3 : IChannel
{
private TChannelValue value1;
private TChannelValue value2;
private TChannelValue value3;
public TChannelValue R
{
readonly get
{
if (TChannel1.IsRed)
{
return TChannel1.GetRed(value1);
}
else if (TChannel2.IsRed)
{
return TChannel2.GetRed(value2);
}
else if (TChannel3.IsRed)
{
return TChannel3.GetRed(value3);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfRed<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfRed<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfRed<TChannel3, TChannelValue>(ref value3, value);
}
}
public TChannelValue G
{
readonly get
{
if (TChannel1.IsGreen)
{
return TChannel1.GetGreen(value1);
}
else if (TChannel2.IsGreen)
{
return TChannel2.GetGreen(value2);
}
else if (TChannel3.IsGreen)
{
return TChannel3.GetGreen(value3);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfGreen<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfGreen<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfGreen<TChannel3, TChannelValue>(ref value3, value);
}
}
public TChannelValue B
{
readonly get
{
if (TChannel1.IsBlue)
{
return TChannel1.GetBlue(value1);
}
else if (TChannel2.IsBlue)
{
return TChannel2.GetBlue(value2);
}
else if (TChannel3.IsBlue)
{
return TChannel3.GetBlue(value3);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfBlue<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfBlue<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfBlue<TChannel3, TChannelValue>(ref value3, value);
}
}
public TChannelValue A
{
readonly get
{
if (TChannel1.IsAlpha)
{
return TChannel1.GetAlpha(value1);
}
else if (TChannel2.IsAlpha)
{
return TChannel2.GetAlpha(value2);
}
else if (TChannel3.IsAlpha)
{
return TChannel3.GetAlpha(value3);
}
else
{
return NumericConversion.GetMaximumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfAlpha<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfAlpha<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfAlpha<TChannel3, TChannelValue>(ref value3, value);
}
}
public Color(TChannelValue value1, TChannelValue value2, TChannelValue value3)
{
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
}
public readonly void GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(TChannelValue r, TChannelValue g, TChannelValue b, TChannelValue a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => TChannel1.IsRed || TChannel2.IsRed || TChannel3.IsRed;
static bool IColor.HasGreenChannel => TChannel1.IsGreen || TChannel2.IsGreen || TChannel3.IsGreen;
static bool IColor.HasBlueChannel => TChannel1.IsBlue || TChannel2.IsBlue || TChannel3.IsBlue;
static bool IColor.HasAlphaChannel => TChannel1.IsAlpha || TChannel2.IsAlpha || TChannel3.IsAlpha;
static bool IColor.ChannelsAreFullyUtilized => TChannel1.FullyUtilized && TChannel2.FullyUtilized && TChannel3.FullyUtilized;
static Type IColor.ChannelType => typeof(TChannelValue);
public static Color<TChannelValue, TChannel1, TChannel2, TChannel3> Black
{
get => new(TChannel1.GetBlack<TChannelValue>(), TChannel2.GetBlack<TChannelValue>(), TChannel3.GetBlack<TChannelValue>());
}
public static Color<TChannelValue, TChannel1, TChannel2, TChannel3> White
{
get => new(TChannel1.GetWhite<TChannelValue>(), TChannel2.GetWhite<TChannelValue>(), TChannel3.GetWhite<TChannelValue>());
}
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
+200
View File
@@ -0,0 +1,200 @@
// Auto-generated code. Do not modify manually.
using AssetRipper.TextureDecoder.Rgb.Channels;
namespace AssetRipper.TextureDecoder.Rgb;
public partial struct Color<TChannelValue, TChannel1, TChannel2, TChannel3, TChannel4> : IColor<Color<TChannelValue, TChannel1, TChannel2, TChannel3, TChannel4>, TChannelValue>
where TChannelValue : unmanaged, INumberBase<TChannelValue>, IMinMaxValue<TChannelValue>
where TChannel1 : IChannel
where TChannel2 : IChannel
where TChannel3 : IChannel
where TChannel4 : IChannel
{
private TChannelValue value1;
private TChannelValue value2;
private TChannelValue value3;
private TChannelValue value4;
public TChannelValue R
{
readonly get
{
if (TChannel1.IsRed)
{
return TChannel1.GetRed(value1);
}
else if (TChannel2.IsRed)
{
return TChannel2.GetRed(value2);
}
else if (TChannel3.IsRed)
{
return TChannel3.GetRed(value3);
}
else if (TChannel4.IsRed)
{
return TChannel4.GetRed(value4);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfRed<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfRed<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfRed<TChannel3, TChannelValue>(ref value3, value);
Channel.SetIfRed<TChannel4, TChannelValue>(ref value4, value);
}
}
public TChannelValue G
{
readonly get
{
if (TChannel1.IsGreen)
{
return TChannel1.GetGreen(value1);
}
else if (TChannel2.IsGreen)
{
return TChannel2.GetGreen(value2);
}
else if (TChannel3.IsGreen)
{
return TChannel3.GetGreen(value3);
}
else if (TChannel4.IsGreen)
{
return TChannel4.GetGreen(value4);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfGreen<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfGreen<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfGreen<TChannel3, TChannelValue>(ref value3, value);
Channel.SetIfGreen<TChannel4, TChannelValue>(ref value4, value);
}
}
public TChannelValue B
{
readonly get
{
if (TChannel1.IsBlue)
{
return TChannel1.GetBlue(value1);
}
else if (TChannel2.IsBlue)
{
return TChannel2.GetBlue(value2);
}
else if (TChannel3.IsBlue)
{
return TChannel3.GetBlue(value3);
}
else if (TChannel4.IsBlue)
{
return TChannel4.GetBlue(value4);
}
else
{
return NumericConversion.GetMinimumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfBlue<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfBlue<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfBlue<TChannel3, TChannelValue>(ref value3, value);
Channel.SetIfBlue<TChannel4, TChannelValue>(ref value4, value);
}
}
public TChannelValue A
{
readonly get
{
if (TChannel1.IsAlpha)
{
return TChannel1.GetAlpha(value1);
}
else if (TChannel2.IsAlpha)
{
return TChannel2.GetAlpha(value2);
}
else if (TChannel3.IsAlpha)
{
return TChannel3.GetAlpha(value3);
}
else if (TChannel4.IsAlpha)
{
return TChannel4.GetAlpha(value4);
}
else
{
return NumericConversion.GetMaximumValueSafe<TChannelValue>();
}
}
set
{
Channel.SetIfAlpha<TChannel1, TChannelValue>(ref value1, value);
Channel.SetIfAlpha<TChannel2, TChannelValue>(ref value2, value);
Channel.SetIfAlpha<TChannel3, TChannelValue>(ref value3, value);
Channel.SetIfAlpha<TChannel4, TChannelValue>(ref value4, value);
}
}
public Color(TChannelValue value1, TChannelValue value2, TChannelValue value3, TChannelValue value4)
{
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
this.value4 = value4;
}
public readonly void GetChannels(out TChannelValue r, out TChannelValue g, out TChannelValue b, out TChannelValue a)
{
r = R;
g = G;
b = B;
a = A;
}
public void SetChannels(TChannelValue r, TChannelValue g, TChannelValue b, TChannelValue a)
{
R = r;
G = g;
B = b;
A = a;
}
static bool IColor.HasRedChannel => TChannel1.IsRed || TChannel2.IsRed || TChannel3.IsRed || TChannel4.IsRed;
static bool IColor.HasGreenChannel => TChannel1.IsGreen || TChannel2.IsGreen || TChannel3.IsGreen || TChannel4.IsGreen;
static bool IColor.HasBlueChannel => TChannel1.IsBlue || TChannel2.IsBlue || TChannel3.IsBlue || TChannel4.IsBlue;
static bool IColor.HasAlphaChannel => TChannel1.IsAlpha || TChannel2.IsAlpha || TChannel3.IsAlpha || TChannel4.IsAlpha;
static bool IColor.ChannelsAreFullyUtilized => TChannel1.FullyUtilized && TChannel2.FullyUtilized && TChannel3.FullyUtilized && TChannel4.FullyUtilized;
static Type IColor.ChannelType => typeof(TChannelValue);
public static Color<TChannelValue, TChannel1, TChannel2, TChannel3, TChannel4> Black
{
get => new(TChannel1.GetBlack<TChannelValue>(), TChannel2.GetBlack<TChannelValue>(), TChannel3.GetBlack<TChannelValue>(), TChannel4.GetBlack<TChannelValue>());
}
public static Color<TChannelValue, TChannel1, TChannel2, TChannel3, TChannel4> White
{
get => new(TChannel1.GetWhite<TChannelValue>(), TChannel2.GetWhite<TChannelValue>(), TChannel3.GetWhite<TChannelValue>(), TChannel4.GetWhite<TChannelValue>());
}
public override string ToString()
{
return $"{{ R: {R}, G: {G}, B: {B}, A: {A} }}";
}
}
+4 -4
View File
@@ -10,7 +10,7 @@
/// <typeparam name="TSelf">
/// The implementing type.
/// </typeparam>
/// <typeparam name="TChannel">
/// <typeparam name="TChannelValue">
/// Supported types are:
/// <list type="bullet">
/// <item><see cref="sbyte"/></item>
@@ -32,9 +32,9 @@
/// <item><see cref="decimal"/></item>
/// </list>
/// </typeparam>
public interface IColor<TSelf, TChannel> : IColor<TChannel>
where TSelf : unmanaged, IColor<TSelf, TChannel>
where TChannel : unmanaged
public interface IColor<TSelf, TChannelValue> : IColor<TChannelValue>
where TSelf : unmanaged, IColor<TSelf, TChannelValue>
where TChannelValue : unmanaged
{
/// <summary>
/// A black pixel.
+16 -16
View File
@@ -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);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<IsAotCompatible>true</IsAotCompatible>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<Version>2.1.1</Version>
<Version>2.2.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<Copyright>Copyright © ds5678</Copyright>
<Authors>ds5678</Authors>