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

C語言繪圖程式套件原始碼解析:VGA 256 色模式下的像素、線條與幾何圖形

     編輯:C 語言基礎

前言:直擊顯示記憶體的硬核繪圖

在 GPU 普及前的 DOS 年代,程式設計師必須直接操作 VGA 顯示記憶體 (0xA0000) 才能獲得高效能繪圖。本文解析一套精簡的 C 語言繪圖套件,涵蓋 分頁管理、像素讀寫、Bresenham 畫線、矩形/填滿矩形、中點畫圓 等核心功能,程式碼僅約 200 行,卻展現了底層圖形程式設計的精髓。

環境與常數定義

#include <dos.h>          /* int86(), REGS 聯合體 */
#define MAXX 640           /* 解析度 640x480 */
#define MAXY 480

static float asp_ratio = 1.0f;  /* 長寬比修正,預設 1:1 */

核心底層:VGA 分頁切換與模式設定

VGA 256 色模式 (Mode 0x101) 的顯存僅 64 KB (0xA0000~0xAFFFF),但 640×480=307,200 像素需 300 KB。解決方案是分頁:將顯存劃分為多個 64 KB 頁,透過 BIOS 中斷 0x10 功能 0x4F05 切換可見頁面。

/* 切換顯示頁面 */
static void selectpage(int page)
{
    union REGS r;
    r.x.ax = 0x4F05;
    r.x.bx = 0;
    r.x.dx = page;
    int86(0x10, &r, &r);
}

/* 進入 640x480x256 模式 (VESA VBE 0x101) */
void init256(void)
{
    union REGS r;
    r.x.ax = 0x4F02;
    r.x.bx = 0x101;
    int86(0x10, &r, &r);
}

/* 返回文字模式 (Mode 3) */
void exit256(void)
{
    union REGS r;
    r.h.ah = 0;
    r.h.al = 3;
    int86(0x10, &r, &r);
}

像素讀寫:跨頁邊界的自動處理

putpixel 與 getpixel 核心邏輯相同:計算線性位移 → 判斷是否跨頁 → 必要時切頁 → 直接存取遠指標 0xA0000000L。

void putpixel(int x, int y, int color)
{
    long offset = (long)y * MAXX + x;   /* 線性位移 */
    int page = (int)(offset >> 16);     /* 高 16 bits = 頁號 */
    offset &= 0xFFFF;                   /* 低 16 bits = 頁內位移 */

    static int current_page = -1;       /* 靜態快取目前頁面 */
    if (current_page != page)
        selectpage(current_page = page);

    *( (char far *)0xA0000000L + offset ) = (char)color;
}

int getpixel(int x, int y)
{
    long offset = (long)y * MAXX + x;
    int page = (int)(offset >> 16);
    offset &= 0xFFFF;

    static int current_page = -1;
    if (current_page != page)
        selectpage(current_page = page);

    return (int)*( (char far *)0xA0000000L + offset );
}

關鍵優化:靜態變數 current_page 避免重複切頁系统调用,大幅提升連續繪圖效能。

Bresenham 畫線演算法:整數運算的極致

程式碼實作通用的八向線段,僅使用整數加減與比較,無乘除、無浮點:

void line(int x0, int y0, int x1, int y1, int color)
{
    int dx = abs(x1 - x0), sx = (x0 < x1) ? 1 : -1;
    int dy = abs(y1 - y0), sy = (y0 < y1) ? 1 : -1;
    int err = (dx > dy ? dx : -dy) / 2, e2;

    for (;;) {
        putpixel(x0, y0, color);
        if (x0 == x1 && y0 == y1) break;
        e2 = err;
        if (e2 > -dx) { err -= dy; x0 += sx; }
        if (e2 < dy)  { err += dx; y0 += sy; }
    }
}

原教材版本使用累積誤差變數 x, y 與 distance,邏輯等價但較冗長;上方為標準 Bresenham 精簡寫法,行為一致。

矩形與填滿矩形

/* 空心矩形:四條邊 */
void box(int x0, int y0, int x1, int y1, int color)
{
    for (int x = x0 + 1; x < x1; x++) {
        putpixel(x, y0, color);
        putpixel(x, y1, color);
    }
    for (int y = y0; y <= y1; y++) {
        putpixel(x0, y, color);
        putpixel(x1, y, color);
    }
}

/* 實心矩形:逐列填滿 */
void fillbox(int x0, int y0, int x1, int y1, int color)
{
    for (int y = y0; y <= y1; y++)
        for (int x = x0; x <= x1; x++)
            putpixel(x, y, color);
}

中點畫圓演算法:八分對稱優化

circle 僅計算 1/8 圓弧 (x ≤ y 區域),透過 plot_circle 利用八向對稱一次繪製 8 點,並支援 asp_ratio 修正長寬比:

static void plot_circle(int x, int y, int cx, int cy, int color)
{
    int xs = (int)(x * asp_ratio), xe = (int)((x + 1) * asp_ratio);
    int ys = (int)(y * asp_ratio), ye = (int)((y + 1) * asp_ratio);

    for (int xi = xs; xi < xe; xi++) {
        putpixel(cx + xi, cy + y, color);
        putpixel(cx + xi, cy - y, color);
        putpixel(cx - xi, cy - y, color);
        putpixel(cx - xi, cy + y, color);
    }
    for (int yi = ys; yi < ye; yi++) {
        putpixel(cx + yi, cy + x, color);
        putpixel(cx + yi, cy - x, color);
        putpixel(cx - yi, cy - x, color);
        putpixel(cx - yi, cy + x, color);
    }
}

void circle(int cx, int cy, int r, int color)
{
    int x = 0, y = r, d = 3 - 2 * r;
    while (x < y) {
        plot_circle(x, y, cx, cy, color);
        if (d < 0) d += 4 * x + 6;
        else       { d += 4 * (x - y) + 10; y--; }
        x++;
    }
    if (x == y) plot_circle(x, y, cx, cy, color);  /* 45° 點 */
}

主程式示範

int main(void)
{
    init256();
    box(100, 100, 200, 200, 35);          /* 空心方框 */
    circle(300, 250, 200, 35);              /* 圓 */
    fillbox(200, 200, 350, 350, 35);        /* 實心方框 */
    getch();
    exit256();
    return 0;
}

現代視角的啟示

  • 記憶體映射 I/O:現代 GPU 依然透過 MMIO 讓 CPU 直接寫入指令緩衝區,概念同源。
  • 分頁/雙緩衝:selectpage 即雙緩衝雛形,現代交換鏈本質相同。
  • 整數演算法:Bresenham、中點畫圓至今仍是光柵化基礎,GPU 光柵化器內部邏輯同源。
  • 遠指標消失:32/64-bit 平坦記憶體模型取代分段架構,但「位移計算 + 基址」思維不變。

這套程式碼雖屬 DOS 年代產物,卻浓縮了電腦圖形學最核心的「從數學定義到像素輸出」完整鏈路,值得每位系統程式設計師細讀、重寫、內化。

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