using AssetRipper.Text.SourceGeneration; using AssetRipper.TextureDecoder.SourceGeneration.Common; using System.CodeDom.Compiler; using System.Diagnostics; using System.Text; namespace AssetRipper.TextureDecoder.ColorGenerator; internal static partial class Program { 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/"; /// /// Name, Type, Red, Blue, Green, Alpha, Fully Utilized /// private static readonly List<(string, Type, bool, bool, bool, bool, bool)> CustomColors = new() { ( "ColorARGB16", typeof(byte), true, true, true, true, false ), ( "ColorRGB16", typeof(byte), true, true, true, false, false ), ( "ColorRGB9e5", typeof(double), true, true, true, false, false ), ( "ColorRGBA16", typeof(byte), true, true, true, true, false ), }; private static readonly List GenericColors = [ "ColorR", "ColorRG", "ColorRGB", "ColorRGBA", "ColorA", "ColorARGB", "ColorBGRA", "ColorBGR", ]; static void Main() { foreach (var customColor in CustomColors) { WriteCustomColor(customColor); } foreach (string genericColor in GenericColors) { WriteGenericColor(genericColor); } for (int i = 1; i <= 4; i++) { WriteSuperGenericColor(i); } NumericConversionGenerator.Run(); Console.WriteLine("Done!"); } private static void WriteCustomColor((string, Type, bool, bool, bool, bool, bool) details) { (string name, Type type, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, bool fullyUtilized) = details; Console.WriteLine(name); using IndentedTextWriter writer = IndentedTextWriterFactory.Create(FormatsFolder, name); WriteCustomColor(writer, name, type, hasRed, hasGreen, hasBlue, hasAlpha, fullyUtilized); } private static void WriteGenericColor(string name) { Console.WriteLine(name); using IndentedTextWriter writer = IndentedTextWriterFactory.Create(FormatsFolder, name); WriteGenericColor(writer, name); } 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, FormatsNamespace)) { writer.WriteLine($"public partial struct {name} : IColor<{typeName}>"); using (new CurlyBrackets(writer)) { WriteNonGenericStaticProperties(writer, hasRed, hasGreen, hasBlue, hasAlpha, fullyUtilized, typeName); writer.WriteLineNoTabs(); WriteToString(writer, hasRed, hasGreen, hasBlue, hasAlpha); } } } private static void WriteToString(IndentedTextWriter writer, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha) { writer.WriteLine("public override string ToString()"); using (new CurlyBrackets(writer)) { StringBuilder sb = new(); sb.Append("return $\"{{ "); bool first = true; if (hasRed) { sb.Append("R: {R}"); first = false; } if (hasGreen) { if (!first) { sb.Append(", "); } sb.Append("G: {G}"); first = false; } if (hasBlue) { if (!first) { sb.Append(", "); } sb.Append("B: {B}"); first = false; } if (hasAlpha) { if (!first) { sb.Append(", "); } sb.Append("A: {A}"); } sb.Append(" }}\";"); writer.WriteLine(sb.ToString()); } } private static void WriteNonGenericStaticProperties(IndentedTextWriter writer, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, bool fullyUtilized, string typeName) { 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});"); } private static void WriteBlackWhiteStaticProperties(IndentedTextWriter writer, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, string colorTypeName, string channelTypeName) { string minValue = $"NumericConversion.GetMinimumValueSafe<{channelTypeName}>()"; string maxValue = $"NumericConversion.GetMaximumValueSafe<{channelTypeName}>()"; //Black { writer.Write($"public static {colorTypeName}<{channelTypeName}> Black => new("); int rgbCount = CountRGB(hasRed, hasGreen, hasBlue); for (int i = 0; i < rgbCount; i++) { if (i != 0) { writer.Write(", "); } writer.Write(minValue); } if (hasAlpha) { if (rgbCount != 0) { writer.Write(", "); } writer.Write(maxValue); } writer.WriteLine(");"); } //White if (hasRed || hasGreen || hasBlue) { writer.Write($"public static {colorTypeName}<{channelTypeName}> White => new("); int rgbaCount = CountRGBA(hasRed, hasGreen, hasBlue, hasAlpha); for (int i = 0; i < rgbaCount; i++) { if (i != 0) { writer.Write(", "); } writer.Write(maxValue); } writer.WriteLine(");"); } else { writer.WriteLine($"static {colorTypeName}<{channelTypeName}> IColor<{colorTypeName}<{channelTypeName}>, {channelTypeName}>.White => throw new NotSupportedException();"); } static int CountRGB(bool hasRed, bool hasGreen, bool hasBlue) { return (hasRed ? 1 : 0) + (hasGreen ? 1 : 0) + (hasBlue ? 1 : 0); } static int CountRGBA(bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha) { return CountRGB(hasRed, hasGreen, hasBlue) + (hasAlpha ? 1 : 0); } } private static void WriteGenericColor(IndentedTextWriter writer, string name) { const string typeName = "T"; const string minValue = "NumericConversion.GetMinimumValueSafe()"; const string maxValue = "NumericConversion.GetMaximumValueSafe()"; writer.WriteGeneratedCodeWarning(); writer.WriteLineNoTabs(); writer.WriteFileScopedNamespace(FormatsNamespace); writer.WriteLineNoTabs(); writer.WriteLine($"public partial struct {name} : IColor<{name}, T> where T : unmanaged, INumberBase, IMinMaxValue"); using (new CurlyBrackets(writer)) { Debug.Assert(name.StartsWith("Color", StringComparison.Ordinal)); ReadOnlySpan channels = name.AsSpan()[5..]; foreach (char channel in channels) { WriteProperty(writer, true, typeName, channel, minValue); writer.WriteLineNoTabs(); } bool hasRed = channels.Contains('R'); bool hasGreen = channels.Contains('G'); bool hasBlue = channels.Contains('B'); bool hasAlpha = channels.Contains('A'); if (!hasRed) { WriteProperty(writer, hasRed, typeName, 'R', minValue); writer.WriteLineNoTabs(); } if (!hasGreen) { WriteProperty(writer, hasGreen, typeName, 'G', minValue); writer.WriteLineNoTabs(); } if (!hasBlue) { WriteProperty(writer, hasBlue, typeName, 'B', minValue); writer.WriteLineNoTabs(); } if (!hasAlpha) { WriteProperty(writer, hasAlpha, typeName, 'A', maxValue); writer.WriteLineNoTabs(); } WriteConstructor(writer, name, hasRed, hasGreen, hasBlue, hasAlpha, typeName); writer.WriteLineNoTabs(); WriteGetChannels(writer, typeName); writer.WriteLineNoTabs(); WriteSetChannels(writer, typeName, hasRed, hasGreen, hasBlue, hasAlpha); writer.WriteLineNoTabs(); WriteNonGenericStaticProperties(writer, hasRed, hasGreen, hasBlue, hasAlpha, true, typeName); writer.WriteLineNoTabs(); WriteBlackWhiteStaticProperties(writer, hasRed, hasGreen, hasBlue, hasAlpha, name, typeName); writer.WriteLineNoTabs(); WriteToString(writer, hasRed, hasGreen, hasBlue, hasAlpha); } } 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?>($"{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}("); if (hasRed) { writer.Write($"{channelTypeName} r"); if (hasGreen || hasBlue || hasAlpha) { writer.Write(", "); } } if (hasGreen) { writer.Write($"{channelTypeName} g"); if (hasBlue || hasAlpha) { writer.Write(", "); } } if (hasBlue) { writer.Write($"{channelTypeName} b"); if (hasAlpha) { writer.Write(", "); } } if (hasAlpha) { writer.Write($"{channelTypeName} a"); } writer.WriteLine(")"); using (new CurlyBrackets(writer)) { if (hasRed) { writer.WriteLine("R = r;"); } if (hasGreen) { writer.WriteLine("G = g;"); } if (hasBlue) { writer.WriteLine("B = b;"); } if (hasAlpha) { writer.WriteLine("A = a;"); } } } private static void WriteGetChannels(IndentedTextWriter writer, string typeName) { writer.WriteLine($"public readonly void GetChannels(out {typeName} r, out {typeName} g, out {typeName} b, out {typeName} a)"); using (new CurlyBrackets(writer)) { writer.WriteLine("r = R;"); writer.WriteLine("g = G;"); writer.WriteLine("b = B;"); writer.WriteLine("a = A;"); } } private static void WriteSetChannels(IndentedTextWriter writer, string typeName, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha) { writer.WriteLine($"public void SetChannels({typeName} r, {typeName} g, {typeName} b, {typeName} a)"); using (new CurlyBrackets(writer)) { if (hasRed) { writer.WriteLine("R = r;"); } if (hasGreen) { writer.WriteLine("G = g;"); } if (hasBlue) { writer.WriteLine("B = b;"); } if (hasAlpha) { writer.WriteLine("A = a;"); } } } private static void WriteProperty(IndentedTextWriter writer, bool hasColor, string typeName, char channel, string defaultValue) { if (hasColor) { writer.WriteLine($"public {typeName} {channel} {{ get; set; }}"); } else { writer.WriteLine($"public readonly {typeName} {channel} "); using (new CurlyBrackets(writer)) { writer.WriteLine($"get => {defaultValue};"); writer.WriteLine("set { }"); } } } private static string ToLowerString(this bool value) { return value ? "true" : "false"; } }