Loading TestV/Playgrounds/AllColor.cs +152 −37 Original line number Diff line number Diff line using Spectre.Console; using Spectre.Console; using TestV.Tools; namespace TestV.Playgrounds; Loading @@ -6,59 +7,173 @@ public class AllColor : IPlayground { public string LaunchCmd { get; } = "allcol"; public string? Name { get; } = "展示所有颜色"; public string? Description { get; } = "相比 16 色内置控制台,Spectre 支持更多颜色。"; public string? Description { get; } = "在 RGB/HSV/LAB 颜色空间生成渐变(默认 RGB)。"; private enum Mode { Rgb, Hsv, Lab } public async Task Run(string[] args) { var cpc = args.Length > 0 ? byte.Parse(args[0]) : 255; var ct = new CancellationTokenSource(); int cpc = 255; var mode = Mode.Rgb; _ = Task.Run(() => for (int i = 0; i < args.Length; i++) { var a = args[i]; if (a is "--mode" or "-m") { while (Console.ReadKey().Key == ConsoleKey.Q) if (i + 1 >= args.Length) { Errors.NoArgs(1); return; } if (!TryParseMode(args[++i], out mode)) { Errors.NotArrow("模式", "rgb", "hsv", "lab"); return; } } else if (a.StartsWith("--mode=")) { if (ct is null) if (!TryParseMode(a["--mode=".Length..], out mode)) { Errors.NotArrow("模式", "rgb", "hsv", "lab"); return; } } else if (int.TryParse(a, out var n) && n > 0) cpc = n; else { Errors.NotArrow("参数", "--mode", "-m", "--mode=", "<正整数>"); return; } } ct?.Cancel(); // HSV/LAB 各通道范围与 RGB 不同,cpc 仅表示每通道采样数 var ct = new CancellationTokenSource(); _ = Task.Run(() => { while (!ct.Token.IsCancellationRequested) { if (Console.ReadKey(true).Key == ConsoleKey.Q) { ct.Cancel(); return; } } }, ct.Token); await MakeGradient(cpc, ct.Token); ct?.Cancel(); AnsiConsole.MarkupLine($"[grey]模式: {mode} 每通道采样: {cpc} (按 Q 退出)[/]"); try { await MakeGradient(mode, cpc, ct.Token); } catch (OperationCanceledException) { } AnsiConsole.WriteLine(); } private async Task MakeGradient(int colorsPerChannel, CancellationToken ct = default) private static bool TryParseMode(string s, out Mode mode) { var i = 255 / colorsPerChannel; mode = default; if (s.Equals("rgb", StringComparison.OrdinalIgnoreCase)) { mode = Mode.Rgb; return true; } if (s.Equals("hsv", StringComparison.OrdinalIgnoreCase)) { mode = Mode.Hsv; return true; } if (s.Equals("lab", StringComparison.OrdinalIgnoreCase)) { mode = Mode.Lab; return true; } return false; } try private static async Task MakeGradient(Mode mode, int cpc, CancellationToken ct) { for (float r = 0; r < 255; r += i) // 三个通道:外/中/内层循环,每层 cpc 个采样 for (int i = 0; i < cpc; i++) for (int j = 0; j < cpc; j++) for (int k = 0; k < cpc; k++) { ct.ThrowIfCancellationRequested(); for (float g = 0; g < 255; g += i) var (r, g, b) = mode switch { ct.ThrowIfCancellationRequested(); Mode.Rgb => RgbSample(i, j, k, cpc), Mode.Hsv => HsvToRgb(HsvSample(i, j, k, cpc)), Mode.Lab => LabToRgb(LabSample(i, j, k, cpc)), _ => throw new ArgumentOutOfRangeException() }; AnsiConsole.Write(new Text(" ", new Style(null, new Color(r, g, b)))); } await Task.CompletedTask; } for (float b = 0; b < 255; b += i) // ---------- RGB ---------- private static (byte r, byte g, byte b) RgbSample(int i, int j, int k, int cpc) { ct.ThrowIfCancellationRequested(); var tr = (byte)Math.Clamp(Math.Floor(r), 0, 255); var tg = (byte)Math.Clamp(Math.Floor(g), 0, 255); var tb = (byte)Math.Clamp(Math.Floor(b), 0, 255); byte Map(int v) => (byte)Math.Clamp(v * 255 / Math.Max(1, cpc - 1), 0, 255); return (Map(i), Map(j), Map(k)); } var tx = new Text(" ", new Style(null, new Color(tr, tg, tb))); AnsiConsole.Write(tx); // ---------- HSV ---------- // H: 0..360, S: 0..1, V: 0..1 private static (float h, float s, float v) HsvSample(int i, int j, int k, int cpc) { float h = i * 360f / Math.Max(1, cpc); float s = cpc == 1 ? 1f : j / (float)(cpc - 1); float v = cpc == 1 ? 1f : k / (float)(cpc - 1); return (h, s, v); } // 标准 HSV → RGB private static (byte r, byte g, byte b) HsvToRgb((float h, float s, float v) hsv) { float h = hsv.h / 60f; float s = hsv.s, v = hsv.v; float c = v * s; float x = c * (1 - Math.Abs(h % 2 - 1)); float m = v - c; float r1, g1, b1; switch ((int)Math.Floor(h) % 6) { case 0: r1 = c; g1 = x; b1 = 0; break; case 1: r1 = x; g1 = c; b1 = 0; break; case 2: r1 = 0; g1 = c; b1 = x; break; case 3: r1 = 0; g1 = x; b1 = c; break; case 4: r1 = x; g1 = 0; b1 = c; break; default: r1 = c; g1 = 0; b1 = x; break; } return ( (byte)Math.Clamp((r1 + m) * 255, 0, 255), (byte)Math.Clamp((g1 + m) * 255, 0, 255), (byte)Math.Clamp((b1 + m) * 255, 0, 255) ); } // ---------- LAB ---------- // L: 0..100, a: -128..127, b: -128..127 private static (float l, float a, float b) LabSample(int i, int j, int k, int cpc) { float l = cpc == 1 ? 50f : i * 100f / (cpc - 1); float a = cpc == 1 ? 0f : j * 255f / (cpc - 1) - 128f; float b = cpc == 1 ? 0f : k * 255f / (cpc - 1) - 128f; return (l, a, b); } catch (OperationCanceledException) // LAB → XYZ → RGB (sRGB, D65 白点) private static (byte r, byte g, byte b) LabToRgb((float l, float a, float b) lab) { float fy = (lab.l + 16f) / 116f; float fx = lab.a / 500f + fy; float fz = fy - lab.b / 200f; float Xn = 95.047f, Yn = 100.000f, Zn = 108.883f; float x = Xn * InvF(fx); float y = Yn * InvF(fy); float z = Zn * InvF(fz); // XYZ (D65) → 线性 sRGB float rL = x * 3.2406f + y * -1.5372f + z * -0.4986f; float gL = x * -0.9689f + y * 1.8758f + z * 0.0415f; float bL = x * 0.0557f + y * -0.2040f + z * 1.0570f; // Gamma 校正 byte r = GammaEncode(rL); byte g = GammaEncode(gL); byte b = GammaEncode(bL); return (r, g, b); static float InvF(float t) { float t3 = t * t * t; return t3 > 0.008856f ? t3 : (t - 16f / 116f) / 7.787f; } static byte GammaEncode(float c) { float v = c <= 0.0031308f ? 12.92f * c : 1.055f * MathF.Pow(c, 1f / 2.4f) - 0.055f; return (byte)Math.Clamp(v * 255, 0, 255); } } } Loading
TestV/Playgrounds/AllColor.cs +152 −37 Original line number Diff line number Diff line using Spectre.Console; using Spectre.Console; using TestV.Tools; namespace TestV.Playgrounds; Loading @@ -6,59 +7,173 @@ public class AllColor : IPlayground { public string LaunchCmd { get; } = "allcol"; public string? Name { get; } = "展示所有颜色"; public string? Description { get; } = "相比 16 色内置控制台,Spectre 支持更多颜色。"; public string? Description { get; } = "在 RGB/HSV/LAB 颜色空间生成渐变(默认 RGB)。"; private enum Mode { Rgb, Hsv, Lab } public async Task Run(string[] args) { var cpc = args.Length > 0 ? byte.Parse(args[0]) : 255; var ct = new CancellationTokenSource(); int cpc = 255; var mode = Mode.Rgb; _ = Task.Run(() => for (int i = 0; i < args.Length; i++) { var a = args[i]; if (a is "--mode" or "-m") { while (Console.ReadKey().Key == ConsoleKey.Q) if (i + 1 >= args.Length) { Errors.NoArgs(1); return; } if (!TryParseMode(args[++i], out mode)) { Errors.NotArrow("模式", "rgb", "hsv", "lab"); return; } } else if (a.StartsWith("--mode=")) { if (ct is null) if (!TryParseMode(a["--mode=".Length..], out mode)) { Errors.NotArrow("模式", "rgb", "hsv", "lab"); return; } } else if (int.TryParse(a, out var n) && n > 0) cpc = n; else { Errors.NotArrow("参数", "--mode", "-m", "--mode=", "<正整数>"); return; } } ct?.Cancel(); // HSV/LAB 各通道范围与 RGB 不同,cpc 仅表示每通道采样数 var ct = new CancellationTokenSource(); _ = Task.Run(() => { while (!ct.Token.IsCancellationRequested) { if (Console.ReadKey(true).Key == ConsoleKey.Q) { ct.Cancel(); return; } } }, ct.Token); await MakeGradient(cpc, ct.Token); ct?.Cancel(); AnsiConsole.MarkupLine($"[grey]模式: {mode} 每通道采样: {cpc} (按 Q 退出)[/]"); try { await MakeGradient(mode, cpc, ct.Token); } catch (OperationCanceledException) { } AnsiConsole.WriteLine(); } private async Task MakeGradient(int colorsPerChannel, CancellationToken ct = default) private static bool TryParseMode(string s, out Mode mode) { var i = 255 / colorsPerChannel; mode = default; if (s.Equals("rgb", StringComparison.OrdinalIgnoreCase)) { mode = Mode.Rgb; return true; } if (s.Equals("hsv", StringComparison.OrdinalIgnoreCase)) { mode = Mode.Hsv; return true; } if (s.Equals("lab", StringComparison.OrdinalIgnoreCase)) { mode = Mode.Lab; return true; } return false; } try private static async Task MakeGradient(Mode mode, int cpc, CancellationToken ct) { for (float r = 0; r < 255; r += i) // 三个通道:外/中/内层循环,每层 cpc 个采样 for (int i = 0; i < cpc; i++) for (int j = 0; j < cpc; j++) for (int k = 0; k < cpc; k++) { ct.ThrowIfCancellationRequested(); for (float g = 0; g < 255; g += i) var (r, g, b) = mode switch { ct.ThrowIfCancellationRequested(); Mode.Rgb => RgbSample(i, j, k, cpc), Mode.Hsv => HsvToRgb(HsvSample(i, j, k, cpc)), Mode.Lab => LabToRgb(LabSample(i, j, k, cpc)), _ => throw new ArgumentOutOfRangeException() }; AnsiConsole.Write(new Text(" ", new Style(null, new Color(r, g, b)))); } await Task.CompletedTask; } for (float b = 0; b < 255; b += i) // ---------- RGB ---------- private static (byte r, byte g, byte b) RgbSample(int i, int j, int k, int cpc) { ct.ThrowIfCancellationRequested(); var tr = (byte)Math.Clamp(Math.Floor(r), 0, 255); var tg = (byte)Math.Clamp(Math.Floor(g), 0, 255); var tb = (byte)Math.Clamp(Math.Floor(b), 0, 255); byte Map(int v) => (byte)Math.Clamp(v * 255 / Math.Max(1, cpc - 1), 0, 255); return (Map(i), Map(j), Map(k)); } var tx = new Text(" ", new Style(null, new Color(tr, tg, tb))); AnsiConsole.Write(tx); // ---------- HSV ---------- // H: 0..360, S: 0..1, V: 0..1 private static (float h, float s, float v) HsvSample(int i, int j, int k, int cpc) { float h = i * 360f / Math.Max(1, cpc); float s = cpc == 1 ? 1f : j / (float)(cpc - 1); float v = cpc == 1 ? 1f : k / (float)(cpc - 1); return (h, s, v); } // 标准 HSV → RGB private static (byte r, byte g, byte b) HsvToRgb((float h, float s, float v) hsv) { float h = hsv.h / 60f; float s = hsv.s, v = hsv.v; float c = v * s; float x = c * (1 - Math.Abs(h % 2 - 1)); float m = v - c; float r1, g1, b1; switch ((int)Math.Floor(h) % 6) { case 0: r1 = c; g1 = x; b1 = 0; break; case 1: r1 = x; g1 = c; b1 = 0; break; case 2: r1 = 0; g1 = c; b1 = x; break; case 3: r1 = 0; g1 = x; b1 = c; break; case 4: r1 = x; g1 = 0; b1 = c; break; default: r1 = c; g1 = 0; b1 = x; break; } return ( (byte)Math.Clamp((r1 + m) * 255, 0, 255), (byte)Math.Clamp((g1 + m) * 255, 0, 255), (byte)Math.Clamp((b1 + m) * 255, 0, 255) ); } // ---------- LAB ---------- // L: 0..100, a: -128..127, b: -128..127 private static (float l, float a, float b) LabSample(int i, int j, int k, int cpc) { float l = cpc == 1 ? 50f : i * 100f / (cpc - 1); float a = cpc == 1 ? 0f : j * 255f / (cpc - 1) - 128f; float b = cpc == 1 ? 0f : k * 255f / (cpc - 1) - 128f; return (l, a, b); } catch (OperationCanceledException) // LAB → XYZ → RGB (sRGB, D65 白点) private static (byte r, byte g, byte b) LabToRgb((float l, float a, float b) lab) { float fy = (lab.l + 16f) / 116f; float fx = lab.a / 500f + fy; float fz = fy - lab.b / 200f; float Xn = 95.047f, Yn = 100.000f, Zn = 108.883f; float x = Xn * InvF(fx); float y = Yn * InvF(fy); float z = Zn * InvF(fz); // XYZ (D65) → 线性 sRGB float rL = x * 3.2406f + y * -1.5372f + z * -0.4986f; float gL = x * -0.9689f + y * 1.8758f + z * 0.0415f; float bL = x * 0.0557f + y * -0.2040f + z * 1.0570f; // Gamma 校正 byte r = GammaEncode(rL); byte g = GammaEncode(gL); byte b = GammaEncode(bL); return (r, g, b); static float InvF(float t) { float t3 = t * t * t; return t3 > 0.008856f ? t3 : (t - 16f / 116f) / 7.787f; } static byte GammaEncode(float c) { float v = c <= 0.0031308f ? 12.92f * c : 1.055f * MathF.Pow(c, 1f / 2.4f) - 0.055f; return (byte)Math.Clamp(v * 255, 0, 255); } } }