必须设置TextRenderingHint.AntiAliasGridFit以启用抗锯齿,否则文字边缘发虚;半透明水印需用Color.FromArgb构造Brush;字体单位为像素且需随图片缩放调整;单行文字居中须手动计算坐标;PNG水印灰边源于Alpha预乘,应改用LockBits或重建Bitmap;Graphics和Bitmap须显式Dispose,Font可复用。

Graphics.DrawString 绘制文字前必须设置抗锯齿模式
直接用 Graphics.DrawString 往图片上写文字,常出现文字边缘发虚、断笔或完全看不清——根本不是字体问题,而是默认渲染模式没调。GDI+ 默认用 TextRenderingHint.SystemDefault,在非屏幕设备(比如内存位图)上会降级为位图字体或跳过抗锯齿。
- 务必在绘制前设
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit - 如果水印要半透明,
Brush必须用Color.FromArgb(alpha, r, g, b)构造,不能只改Pen透明度(DrawString 不走 Pen) - 字体大小单位是像素,不是点(pt),传 12 并不等于 Word 里的 12pt;缩放图片时需同步调整字号,否则水印可能糊成一片
文字居中对齐需要手动计算位置,不是靠 StringFormat.Alignment
StringFormat.Alignment 和 LineAlignment 只控制文本块内多行之间的相对位置,对单行文字“居中到某矩形区域”毫无作用。常见错误是传个 new Rectangle(0, 0, width, height) 就以为文字会自动居中,结果全挤在左上角。
- 用
graphics.MeasureString(text, font)拿实际宽高(注意:它测的是逻辑尺寸,受 DPI 影响) - 目标区域中心点是
(x + width / 2, y + height / 2),文字基线中心 ≈(centerX - measuredWidth / 2, centerY + font.SizeInPoints * 0.35f)(0.35 是经验系数,因字体 ascender/descender 不同) - 更稳的做法:用
graphics.MeasureCharacterRanges+Region.GetBounds精确获取文本外框,但开销大,水印场景通常没必要
DrawString 在 PNG 透明背景上显示异常的根源
给 PNG 图片加水印后,文字周围出现灰边或色块,尤其在浅色文字+深色背景组合下明显。这不是 Graphics bug,而是 PNG 解码时 Alpha 通道被预乘(premultiplied alpha)导致颜色值失真。
- 加载原图时用
Image.FromFile会触发默认解码,Alpha 已被处理;应改用Bitmap.LockBits手动读取原始像素,或加载后立刻复制到新Bitmap并指定PixelFormat.Format32bppArgb - 绘制前确保
graphics.CompositingMode = CompositingMode.SourceOver(默认值,但某些封装库会改) - 避免在已有 Alpha 的图层上反复 DrawString —— 每次绘制都会叠加一次混合,文字边缘渐变会越来越脏
批量加水印时 Graphics 对象泄漏和性能卡点
循环里对每张图都 new Bitmap → Graphics → DrawString → Save,跑几百张就内存暴涨甚至 OOM。问题不在 DrawString 本身,而在未释放 GDI+ 句柄。
-
Graphics必须显式Dispose(),用using最安全;Bitmap同理,别依赖 GC - 不要每次重绘都 new Font —— 字体对象可复用,它是线程安全的;把
Font提到循环外 - 如果只是加固定文字水印,考虑用
TextureBrush预先画好水印图案,再用Graphics.DrawImage贴图,比逐帧 DrawString 快 3–5 倍
水印位置偏移、透明度不生效、批量处理卡死……这些问题几乎都卡在 Graphics 生命周期、像素格式、坐标换算这三个地方。调通一个,其他基本就顺了。










