You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GDI(非GDI+)如何实现填充不透明、描边半透明的绘制效果

原生GDI实现不透明填充+可调透明度描边方案

原生GDI本身不支持带Alpha通道的画笔对象,直接调用RoundRect无法同时满足「内部填充完全不透明、轮廓描边透明度可调」的需求,可通过32位ARGB临时位图+AlphaBlend混合的方案实现,全程仅用原生GDI接口,无需依赖GDI+。

现有参考代码

当前圆角矩形绘制调用:

RoundRect(hdc, 0, 0, 1000, 1000, 50, 50);

当前描边画笔创建逻辑:

CreatePen(PS_SOLID, 15, RGB(5, 61, 88));

实现步骤

  • 创建与目标DC兼容的临时内存DC,生成32位顶-down格式的ARGB DIB位图选入临时DC,将位图所有像素初始化为全透明(Alpha值为0)。
  • 绘制完全不透明的内部填充:将空画笔(NULL_PEN)、目标填充色的纯色画刷选入临时DC,调用RoundRect绘制填充区域,这部分像素保持Alpha=255的完全不透明状态。
  • 绘制纯色描边基底:将空画刷(NULL_BRUSH)、描边色的纯色画笔选入临时DC,用和填充完全一致的坐标参数调用RoundRect,仅绘制轮廓部分,不会覆盖已完成的填充区域。
    • 若填充色和描边色可能重复,可先用任意不会出现在填充区域的标记色(如RGB(255,0,255)洋红)创建画笔绘制描边,后续替换为目标描边色即可,避免描边像素识别错误。
  • 调整描边像素透明度:遍历临时位图的所有像素,识别出属于描边部分的像素,将其Alpha值设置为0-255区间内需要的透明度,同时按照AlphaBlend的预乘Alpha要求,将像素的R/G/B值乘以Alpha/255;填充区域像素保持Alpha=255不变,透明区域保持Alpha=0不变。
  • 调用AlphaBlend将临时DC的内容按Alpha通道混合到原始目标DC上,完成最终绘制,最后释放所有创建的GDI资源。

核心实现代码示例

// 可配置参数
const int RECT_W = 1000;
const int RECT_H = 1000;
const int CORNER_RADIUS = 50;
const int STROKE_WIDTH = 15;
const COLORREF FILL_COLOR = RGB(255,255,255); // 替换为实际填充色
const COLORREF STROKE_COLOR = RGB(5,61,88);   // 替换为实际描边色
const BYTE STROKE_ALPHA = 128;                // 0-255描边透明度,按需调整

// 1. 初始化临时32位ARGB位图
HDC hMemDC = CreateCompatibleDC(hdc);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = RECT_W;
bmi.bmiHeader.biHeight = -RECT_H; // 顶-down格式,避免坐标翻转
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
DWORD* pPixels = NULL;
HBITMAP hMemBmp = CreateDIBSection(hMemDC, &bmi, DIB_RGB_COLORS, (void**)&pPixels, NULL, 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hMemBmp);
memset(pPixels, 0, RECT_W * RECT_H * sizeof(DWORD)); // 初始全透明

// 2. 绘制不透明填充
HPEN hNullPen = (HPEN)GetStockObject(NULL_PEN);
HBRUSH hNullBrush = (HBRUSH)GetStockObject(NULL_BRUSH);
HBRUSH hFillBrush = CreateSolidBrush(FILL_COLOR);
HPEN hOldPen = (HPEN)SelectObject(hMemDC, hNullPen);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hMemDC, hFillBrush);
RoundRect(hMemDC, 0, 0, RECT_W, RECT_H, CORNER_RADIUS, CORNER_RADIUS);
DeleteObject(hFillBrush);

// 3. 绘制描边基底
const COLORREF MARK_COLOR = RGB(255,0,255); // 标记色,避免和填充色冲突
HPEN hMarkPen = CreatePen(PS_SOLID, STROKE_WIDTH, MARK_COLOR);
SelectObject(hMemDC, hMarkPen);
SelectObject(hMemDC, hNullBrush);
RoundRect(hMemDC, 0, 0, RECT_W, RECT_H, CORNER_RADIUS, CORNER_RADIUS);
DeleteObject(hMarkPen);

// 4. 处理描边透明度与预乘Alpha
const int r_stroke = GetRValue(STROKE_COLOR);
const int g_stroke = GetGValue(STROKE_COLOR);
const int b_stroke = GetBValue(STROKE_COLOR);
const int r_mark = GetRValue(MARK_COLOR);
const int g_mark = GetGValue(MARK_COLOR);
const int b_mark = GetBValue(MARK_COLOR);
const int totalPixels = RECT_W * RECT_H;
for (int i = 0; i < totalPixels; i++) {
    DWORD px = pPixels[i];
    BYTE pa = (px >> 24) & 0xFF;
    if (pa == 0) continue; // 跳过透明区域
    BYTE pr = (px >> 16) & 0xFF;
    BYTE pg = (px >> 8) & 0xFF;
    BYTE pb = px & 0xFF;
    // 识别描边像素,替换颜色并设置透明度
    if (pr == r_mark && pg == g_mark && pb == b_mark) {
        BYTE r = (r_stroke * STROKE_ALPHA) / 255;
        BYTE g = (g_stroke * STROKE_ALPHA) / 255;
        BYTE b = (b_stroke * STROKE_ALPHA) / 255;
        pPixels[i] = (STROKE_ALPHA << 24) | (r << 16) | (g << 8) | b;
    }
}

// 5. 混合到目标DC
BLENDFUNCTION bf = {0};
bf.BlendOp = AC_SRC_OVER;
bf.SourceConstantAlpha = 255;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(hdc, 0, 0, RECT_W, RECT_H, hMemDC, 0, 0, RECT_W, RECT_H, bf);

// 6. 资源释放
SelectObject(hMemDC, hOldPen);
SelectObject(hMemDC, hOldBrush);
SelectObject(hMemDC, hOldBmp);
DeleteObject(hMemBmp);
DeleteDC(hMemDC);

方案说明

  • 描边对齐逻辑和原生GDI完全一致,宽画笔默认居中对齐到图形边缘,不会出现填充和描边错位、边缘缝隙的问题。
  • 描边透明度可在0-255区间任意调整,填充区域始终保持100%不透明,绘制效果和原生GDI无视觉差异。
  • 若需要更高性能,可跳过逐像素遍历步骤,单独创建描边层位图做Alpha填充后再和填充层混合,适合大尺寸图形的高频绘制场景。

内容的提问来源于stack exchange,提问作者ilw

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:36:18