.net 8 + SkiaSharp 实现 黑底,浅色字体 6 位字符,扭曲,数字+字母,粘连+干扰线的图形验证码。

先看效果:

image

 

image

 

image

 

image

 实现代码如下:

using SkiaSharp; // 4.151.1
public class verify_code
{
    private static readonly char[] character_table =
    {
        '1','2','3','4','5','6','7','8','9',
        'A',    'C','D',    'F',
        'H',    'J','K','L',    'N',
                'P',    'R','S','T',
        'U','V',        'X','Y','Z',
    };

    private static readonly Random rnd = new Random();
    private static readonly double[,] wave_matrix;
    private static readonly SKSizeI wave_matrix_size;

    private static readonly SKColor[] colors =
    {
        SKColors.Red, SKColors.LimeGreen, SKColors.DeepSkyBlue,
        SKColors.Yellow, SKColors.LightGray, SKColors.Orange
    };

    static verify_code()
    {
        wave_matrix_size = new SKSizeI(600, 600);
        wave_matrix = new double[wave_matrix_size.Width, wave_matrix_size.Height];

        for (int i = 0; i < wave_matrix_size.Width; i++)
        {
            for (int j = 0; j < wave_matrix_size.Height; j++)
            {
                wave_matrix[i, j] = (Math.Sin((i + j) * Math.PI / 130.0) + 1.0) / 2.0;
            }
        }
    }

    public static string NextCode(int length = 6)
    {
        return string.Concat(Enumerable.Range(0, length)
            .Select(_ => character_table[rnd.Next(character_table.Length)]));
    }

    public static SKBitmap NextImage(int length = 6, int width = 190, int height = 80)
    {
        string code = NextCode(length);
        return NextImage(code, width, height);
    }

    // ==================== 1. 修改 NextImage 方法(调用单条干扰线) ====================
    public static SKBitmap NextImage(string code, int width = 240, int height = 100)
    {
        var color = colors[rnd.Next(colors.Length)];

        using var font = new SKFont(SKTypeface.FromFamilyName("Arial", SKFontStyle.BoldItalic), 52);
        using var paint = new SKPaint
        {
            Color = color,
            IsAntialias = true,
            Style = SKPaintStyle.Fill,
        };

        font.MeasureText(code, out var text_bounds);

        float spacing = -9.5f; // 粘连度

        using var bitmap = new SKBitmap(width, height);
        using var canvas = new SKCanvas(bitmap);

        canvas.Clear(SKColors.Black);

        // ==================== 关键调整:整体靠左 ====================
        float total_text_width = text_bounds.Width + spacing * (code.Length - 1);
        float x = (width - total_text_width) / 2f - 12;   // ← 改为 -12(靠左),之前是 +5

        float y = height * 0.68f;

        foreach (char c in code)
        {
            string ch = c.ToString();
            canvas.DrawText(ch, x, y, SKTextAlign.Left, font, paint);
            x += font.MeasureText(ch) + spacing;
        }

        draw_noise_line(canvas, width, height, color);

        return apply_wave_distortion(bitmap);
    }

    // ==================== 2. 新增单条干扰线方法 ====================
    private static void draw_noise_line(SKCanvas canvas, int width, int height, SKColor color)
    {
        using var paint = new SKPaint
        {
            Color = color.WithAlpha(200),
            StrokeWidth = 2.8f,
            Style = SKPaintStyle.Stroke,
            IsAntialias = true,
        };

        var builder = new SKPathBuilder();
        builder.MoveTo(rnd.Next(width / 4), rnd.Next(height));

        // 绘制一条平滑的曲线
        builder.CubicTo(
            rnd.Next(width * 2 / 5, width * 3 / 5), rnd.Next(height / 4, height * 3 / 4),
            rnd.Next(width * 2 / 5, width * 3 / 5), rnd.Next(height / 4, height * 3 / 4),
            rnd.Next(width * 3 / 4, width), rnd.Next(height));

        canvas.DrawPath(builder.Detach(), paint);
    }

    private static SKBitmap apply_wave_distortion(SKBitmap source)
    {
        int w = source.Width;
        int h = source.Height;
        var result = new SKBitmap(w, h);
        using (var canvas = new SKCanvas(result))
            canvas.Clear(SKColors.Black);

        int xi = rnd.Next(wave_matrix_size.Width - w);
        int yi = rnd.Next(wave_matrix_size.Width - w);
        int xj = rnd.Next(wave_matrix_size.Height - h);
        int yj = rnd.Next(wave_matrix_size.Height - h);

        for (int i = 0; i < w; i++)
        {
            for (int j = 0; j < h; j++)
            {
                int nx = i - (int)(wave_matrix[xi + i, xj + j] * 12);
                int ny = j - (int)(wave_matrix[yi + i, yj + j] * 12);

                if (nx >= 0 && nx < w && ny >= 0 && ny < h)
                {
                    result.SetPixel(i, j, source.GetPixel(nx, ny));
                }
            }
        }
        return result;
    }
}

控制中调用方法:

public IActionResult index()
{
    // 1. 生成随机验证码文本
    string code = verify_code.NextCode();

    // 2. 存入 Session(后续验证用)
    HttpContext.Session.SetString("validate_code", code);

    // 3. 生成验证码图片(SKBitmap)
    using var bitmap = verify_code.NextImage(code, width: 240, height: 100);

    // 4. 编码为 PNG 并返回
    using var image = SKImage.FromBitmap(bitmap);
    using var data = image.Encode(SKEncodedImageFormat.Png, 100);
    using var ms = new MemoryStream();
    data.SaveTo(ms);
    return File(ms.ToArray(), "image/png");
}

验证方法:

string? validate_code = HttpContext.Session.GetString("validate_code");
HttpContext.Session.SetString("validate_code", "");
if (validate_code == null || string.IsNullOrEmpty(vercode))
{
    var fail = new
    {
        code = 200,
        msg = "请重新登录",
        success = false
    };
    return Json(fail);
}
else
{
    if (vercode.ToUpper() != validate_code.ToUpper())
    {
        var fail = new
        {
            code = 200,
            msg = "图形验证码错误",
            success = false
        };
        return Json(fail);
    }
}
// 下面是你自己的逻辑 ...

 

posted @ 2026-07-18 10:53  威流  阅读(11)  评论(0)    收藏  举报