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

C# GDI+ 繪圖基礎練習(九):三大坐標系統、Graphics 建立、矩形填充、圖片裁剪變換與滾動瀏覽

     編輯:關於C語言

前言

本文整理自《C# 網絡應用編程基礎練習題與答案(九)》,系統梳理 **GDI+ 三大坐標系統**、**Graphics 物件獲取方式**、**繪圖筆刷與變換**、**PictureBox/Panel 滾動瀏覽大圖** 等核心知識點,並提供可直接運行的 WinForms 範例碼。

一、GDI+ 三大二維坐標系統

坐標系統原點位置單位可變換用途
世界坐標 (World)邏輯定義 (如文檔左上角)自訂 (英寸、毫米、像素...)✅ 平移、旋轉、縮放 (`Graphics.Transform`)邏輯繪圖座標,與裝置無關
頁面坐標 (Page)用戶端區域左上角 (0,0)**由 `PageUnit` / `PageScale` 設定** (預設像素)❌ 固定參考標準世界座標經 `Transform` 轉換後的中介座標
裝置坐標 (Device)螢幕/印表機物理左上角**像素 / 點 (1/72 吋)**❌ 硬體決定最終光柵化輸出座標

流向:世界座標 → (`Graphics.Transform`) → 頁面座標 → (`PageUnit/Scale`) → 裝置座標。

共同點:皆為左手制 (X 右正、Y 下正),原點 (0,0)。

二、建立 Graphics 物件的三種方式

  1. Paint 事件參數:`e.Graphics` — **最推薦**,自動釋放、裁剪區域正確、雙緩衝配合佳。
    `private void Form_Paint(object sender, PaintEventArgs e) { var g = e.Graphics; ... }`
  2. 控制項 `CreateGraphics()`** 隨時取得 — **需自行 `Dispose()`**,無雙緩衝、重繪會丟失,**僅用於即時回饋/非持久繪圖**。
    `using (var g = this.CreateGraphics()) { g.DrawLine(...); }`
  3. `Graphics.FromImage(Image)`** — 繪製到 **記憶體位圖** (離屏繪圖/產生圖檔)。
    `using (var bmp = new Bitmap(200,200)) using (var g = Graphics.FromImage(bmp)) { ... }`

三、紅黑相間填充多個矩形

private void Form_Paint(object sender, PaintEventArgs e)
{
    var g = e.Graphics;
    // 交叉斜線筆刷:紅前景、黑背景
    using (var brush = new HatchBrush(HatchStyle.Cross, Color.Red, Color.Black))
    {
        var rects = new[]
        {
            new Rectangle(0, 0, 50, 100),
            new Rectangle(50, 100, 100, 50),
            new Rectangle(150, 150, 50, 100)
        };
        g.FillRectangles(brush, rects); // 一次繪製陣列,效能優於迴圈
    }
}

四、Label 以矩形/橢圓/圓形裁剪顯示圖片

核心:**`TextureBrush` + `FillRectangle/Ellipse` + `WrapMode.Clamp` (邊緣拉伸不平鋪)**

private string _imagePath = "";
private int _shapeMode = 0; // 1=矩形, 2=橢圓, 3=圓形

private void btnOpen_Click(object sender, EventArgs e)
{
    if (openFileDialog.ShowDialog() == DialogResult.OK)
    {
        _imagePath = openFileDialog.FileName;
        label.Invalidate(); // 觸發重繪
    }
}

private void label_Paint(object sender, PaintEventArgs e)
{
    if (string.IsNullOrEmpty(_imagePath)) return;
    
    using (var bmp = new Bitmap(_imagePath))
    using (var brush = new TextureBrush(bmp, WrapMode.Clamp))
    {
        var g = e.Graphics;
        var rect = label.ClientRectangle;

        switch (_shapeMode)
        {
            case 1: // 矩形填滿
                g.FillRectangle(brush, rect);
                break;
            case 2: // 橢圓填滿控制項範圍
                g.FillEllipse(brush, rect);
                break;
            case 3: // 正圓:以短邊為直徑,置中
                int d = Math.Min(rect.Width, rect.Height);
                int x = (rect.Width - d) / 2;
                int y = (rect.Height - d) / 2;
                g.FillEllipse(brush, x, y, d, d);
                break;
        }
    }
}

// 按鈕切換模式
private void btnRect_Click(object s, EventArgs e) { _shapeMode = 1; label.Invalidate(); }
private void btnEllipse_Click(object s, EventArgs e) { _shapeMode = 2; label.Invalidate(); }
private void btnCircle_Click(object s, EventArgs e) { _shapeMode = 3; label.Invalidate(); }

五、PictureBox + Panel 實現大圖滾動瀏覽

**關鍵**:`Panel.AutoScroll = true`,`PictureBox.SizeMode = AutoSize`,將 `PictureBox` 放入 `Panel`,圖片大於 Panel 時自動出現捲軸。

// 設計器設定:
// panel.AutoScroll = true;
// pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;
// pictureBox.Parent = panel; (或設計器拖入)

private void btnOpenImage_Click(object sender, EventArgs e)
{
    if (openFileDialog.ShowDialog() == DialogResult.OK)
    {
        try
        {
            pictureBox.Image = new Bitmap(openFileDialog.FileName);
            // PictureBox 會自動調整大小適應圖片
            // Panel 偵測到子控制項超出可視區域,自動顯示捲軸
        }
        catch (Exception ex)
        {
            MessageBox.Show($"開啟失敗: {ex.Message}");
        }
    }
}

六、圖片任意角度旋轉、縮放、平移綜合變換

**核心**:`TextureBrush` 的 `ScaleTransform`、`RotateTransform`、`TranslateTransform` **疊加順序影響結果** (先縮放、再旋轉、再平移最直觀)。作用於**筆刷座標系**,而非 `Graphics.Transform`。

// UI:5 個 NumericUpDown 對應 ScaleX, ScaleY, RotateAngle, TranslateX, TranslateY
// 其 ValueChanged 事件均呼叫 pictureBox.Invalidate();

private string _imgPath = "";

private void pictureBox_Paint(object sender, PaintEventArgs e)
{
    if (string.IsNullOrEmpty(_imgPath)) return;

    using (var bmp = new Bitmap(_imgPath))
    using (var brush = new TextureBrush(bmp, WrapMode.Clamp))
    {
        var g = e.Graphics;

        // 1. 縮放 (百分比轉小數)
        float sx = (float)numScaleX.Value / 100f;
        float sy = (float)numScaleY.Value / 100f;
        brush.ScaleTransform(sx, sy);

        // 2. 旋轉 (度數)
        float angle = (float)numRotate.Value;
        brush.RotateTransform(angle);

        // 3. 平移 (像素)
        float tx = (float)numTranslateX.Value;
        float ty = (float)numTranslateY.Value;
        brush.TranslateTransform(tx, ty);

        // 繪製填滿客戶區
        g.FillRectangle(brush, this.ClientRectangle);
    }
}

變換順序陷阱

  • `brush.ScaleTransform` → `RotateTransform` → `TranslateTransform`:
    **縮放中心在原點 (0,0)** → 旋轉中心在原點 → 最後平移。
    若要**以圖片中心旋轉**,需先 `TranslateTransform(-cx, -cy)` → `Rotate` → `TranslateTransform(cx, cy)` → `Scale` (或調整順序)。
  • 建議改用 `Graphics.Transform = new Matrix()` 組合矩陣,語意更清晰:
    `g.Transform = new Matrix(); g.TranslateTransform(cx, cy); g.RotateTransform(angle); g.ScaleTransform(sx, sy); g.TranslateTransform(-cx, -cy);`

總結:GDI+ 繪圖核心清單

  1. **坐標系統轉換鏈路** 清楚:World → Page → Device。
  2. **`Paint` 事件 + `e.Graphics`** 是標準繪圖入口;`CreateGraphics` 僅限即時反饋;`FromImage` 用於離屏/生成檔案。
  3. **筆刷 (`Brush`) 決定填充樣式**:`SolidBrush`、`HatchBrush` (花紋)、`TextureBrush` (圖片)、`LinearGradientBrush`、`PathGradientBrush`。
  4. **`TextureBrush` + 仿射變換** 實現圖片縮放/旋轉/平移/裁剪,注意 `WrapMode` 控制邊緣行為。
  5. **大圖滾動**:`Panel.AutoScroll=true` + `PictureBox.SizeMode=AutoSize` + `PictureBox` 置於 `Panel` 內。
  6. **資源釋放**:`Bitmap`、`Brush`、`Pen`、`Graphics` (非 `e.Graphics`) 必須 `using` / `Dispose()`。
  7. **高效能/複雜場景** 改用 **WPF (DirectX)** / **SkiaSharp** / **Direct2D** — GDI+ 為 CPU 軟體光柵化,大量幾何/圖層易成瓶頸。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved