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
相关产品推荐
相关产品推荐

