首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 繪製螞蟻線選取框:GDI+ LineDDA 動畫實作教學

     編輯:關於C語言

「螞蟻線」是圖形編輯器、截圖工具中常見的虛線選取框動畫效果。本文示範如何在 C# WinForms 中,結合 **GDI+ GraphicsPath** 與 **Win32 API LineDDA** 繪製高效能、可自訂顏色/速度的螞蟻線動畫。

核心原理

  1. GraphicsPath:定義幾何路徑 (矩形、多邊形、曲線...),提供 PathPoints、PathTypes 取得頂點與段落類型。
  2. LineDDA (Digital Differential Analyzer):Win32 GDI 函數,將線段離散化為像素點,**逐點呼叫回呼函數**。我們在回呼中交替繪製不同顏色像素,形成動畫。
  3. Timer 驅動:每 100ms 重繪一次,更新顏色偏移量,產生移動感。

完整實作程式碼

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public partial class AntLineForm : Form
{
    #region P/Invoke
    private delegate void LineDdaProc(int x, int y, IntPtr lpData);

    [DllImport("gdi32.dll")]
    private static extern int LineDDA(int xStart, int yStart, int xEnd, int yEnd, 
        LineDdaProc lpLineFunc, IntPtr lpData);

    [DllImport("gdi32.dll")]
    private static extern int SetPixel(IntPtr hdc, int x, int y, int crColor);
    #endregion

    #region Constants
    private const byte PT_CLOSEFIGURE = 1;
    private const byte PT_LINETO = 2;
    private const byte PT_BEZIERTO = 4;
    private const byte PT_MOVETO = 6;
    #endregion

    #region Fields
    private readonly GraphicsPath _path = new GraphicsPath();
    private IntPtr _hdc = IntPtr.Zero;
    private int _colorOffset = 0;
    private readonly Timer _timer = new Timer { Interval = 100 };
    #endregion

    public AntLineForm()
    {
        InitializeComponent();
        _timer.Tick += OnTimerTick;
    }

    private void btnStart_Click(object sender, EventArgs e)
    {
        // 1. 定義路徑 (這裡示範矩形)
        _path.Reset();
        _path.AddRectangle(new Rectangle(10, 10, 200, 150));

        // 2. 取得裝置內容 (DC)
        _hdc = CreateGraphics().GetHdc();

        // 3. 啟動動畫
        _colorOffset = 0;
        _timer.Start();
    }

    private void OnTimerTick(object sender, EventArgs e)
    {
        if (_hdc == IntPtr.Zero) return;

        // 遍歷路徑每一段,呼叫 LineDDA 繪製
        for (int i = 0; i < _path.PathPoints.Length; i++)
        {
            PointF pt1 = _path.PathPoints[i];
            PointF pt2;
            byte type = _path.PathTypes[i];

            if ((type & PT_CLOSEFIGURE) != 0) // 閉合段:連回 MoveTo 點
            {
                // 找到對應的 MOVETO 點
                for (int j = i; j >= 0; j--)
                {
                    if (_path.PathTypes[j] == PT_MOVETO)
                    {
                        pt2 = _path.PathPoints[j];
                        DrawLineDDA((int)pt1.X, (int)pt1.Y, (int)pt2.X, (int)pt2.Y);
                        break;
                    }
                }
            }
            else if (i == _path.PathPoints.Length - 1) // 最後一點 (非閉合)
            {
                pt2 = _path.PathPoints[0];
                DrawLineDDA((int)pt1.X, (int)pt1.Y, (int)pt2.X, (int)pt2.Y);
            }
            else // 一般線段
            {
                pt2 = _path.PathPoints[i + 1];
                DrawLineDDA((int)pt1.X, (int)pt1.Y, (int)pt2.X, (int)pt2.Y);
            }
        }
    }

    private void DrawLineDDA(int x1, int y1, int x2, int y2)
    {
        LineDDA(x1, y1, x2, y2, LineDdaCallback, IntPtr.Zero);
    }

    private void LineDdaCallback(int x, int y, IntPtr lpData)
    {
        // 三色循環:白 -> 紅 -> 藍 (模擬螞蟻線移動)
        _colorOffset = (_colorOffset + 1) % 15;
        Color color = _colorOffset < 5 ? Color.White : 
                      _colorOffset < 12 ? Color.Red : Color.Blue;

        // SetPixel 需 BGR 格式 (0x00BBGGRR)
        int bgr = color.R | (color.G << 8) | (color.B << 16);
        SetPixel(_hdc, x, y, bgr);
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _timer.Stop();
        if (_hdc != IntPtr.Zero)
        {
            ReleaseHdc(_hdc);
            _hdc = IntPtr.Zero;
        }
        _path.Dispose();
        base.OnFormClosing(e);
    }

    #region Helpers
    [DllImport("user32.dll")]
    private static extern int ReleaseHdc(IntPtr hdc);
    #endregion
}

關鍵技術細節解析

1. LineDDA 回呼簽名

必須符合 void Func(int x, int y, IntPtr data)。委派實例 **_lineDdaProc 必須保持存活** (設為欄位),否則 GC 回收會導致 Crash。

2. 路徑段落類型判斷

  • PT_MOVETO (6):起始點。
  • PT_LINETO (2):直線終點。
  • PT_CLOSEFIGURE (1):標記該點需連回本子路徑起點。實際座標仍是線段終點,需自行找回 MOVETO 點畫閉合線。

3. 色彩模式與速度控制

  • 範例使用 15 幀循環:5 幀白、7 幀紅、3 幀藍。調整模數與閾值即可改變「螞蟻」長度、間距、速度。
  • Timer.Interval = 100 (10 FPS) 可改為 50~200ms 視需求調整。

4. 效能與替代方案

方案優點缺點適用場景
LineDDA + SetPixel (本文)無需雙緩衝、像素級控制、經典做法SetPixel 極慢、大路徑會卡頓、高 DPI 模糊教學、簡單工具、小路徑
GDI+ Pen.DashStyle = DashStyle.Dash + DashOffset 動畫硬體加速、向量級、高 DPI 完美、程式碼極簡虛線樣式固定、難做多色循環**現代專案首選**
WPF DashArray + DoubleAnimation聲明式、GPU 加速、豐富樣式需 WPF 專案WPF 應用

現代 GDI+ 簡易版 (推薦生產環境)

private readonly Pen _antPen = new Pen(Color.Red, 1) { DashStyle = DashStyle.Dash, DashPattern = new float[] { 4, 2 } };
private float _dashOffset = 0;

private void ModernTimer_Tick(object sender, EventArgs e)
{
    _dashOffset += 1f;
    if (_dashOffset > 6) _dashOffset = 0; // 4+2=6
    _antPen.DashOffset = _dashOffset;
    Invalidate(); // 觸發 OnPaint
}

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    e.Graphics.DrawRectangle(_antPen, 10, 10, 200, 150);
}

**優點**:程式碼 < 10 行、無 P/Invoke、高 DPI 自動縮放、CPU 佔用極低。**缺點**:單色虛線、難做「白紅藍」三色循環。若不需花俏配色,**請直接用此法**。

結語

LineDDA 是 Windows 早期 GDI 遺留的像素級繪圖工具,理解其原理有助於掌握光柵化基礎。但在現代高 DPI、硬體加速環境下,**GDI+ Pen.DashOffset 動畫**或 **WPF 動畫**才是正解。本文提供的 LineDDA 完整範例適合教學、維護舊專案或特殊像素特效需求;新專案請優先採用現代向量繪圖 API。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved