Commit abbc870e authored by shrabbit's avatar shrabbit
Browse files

feat(AllColor): 新增 RGB/HSV/LAB 渐变模式,支持 --mode/-m 参数与参数校验

parent 40c1fed9
Loading
Loading
Loading
Loading
+152 −37
Original line number Diff line number Diff line
using Spectre.Console;
using Spectre.Console;
using TestV.Tools;

namespace TestV.Playgrounds;

@@ -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);
        }
    }
}