.NET Framework 4.8中Graphics绘制线条不均匀,疑为单精度转整数舍入错误?
问题:GDI+绘制线条宽窄不均,窗体宽度变化时图案异常(.NET Framework 4.8)
绘制的线条宽窄不均,调整窗体宽度时图案会发生变形,具体表现为部分线条变宽、部分变窄,呈现不均匀的条纹状。当前使用.NET Framework 4.8环境。
复现代码
protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; Color c; int offsetY; g.DrawString("Method 1 - Using ScaleTransform", this.Font, Brushes.Black, 10, 15); g.DrawString("Method 2 - Using ScaleTransform and bitmap", this.Font, Brushes.Black, 10, 100); g.DrawString("Method 3 - Proves it's not a ScaleTransform issue" + Environment.NewLine + "as not using ScaleTransform and doing the scaling manualy", this.Font, Brushes.Black, 10, 185); g.ScaleTransform(System.Convert.ToSingle(this.Width / (double)150), 1); // Method 1 - Using ScaleTransform offsetY = 35; for (int row = 0; row <= 49; row++) { for (int col = 0; col <= 99; col++) { if (col % 2 == 0) c = Color.SkyBlue; else c = Color.Black; g.FillRectangle(new SolidBrush(c), col + 10, row + offsetY, 1, 1); } } // Method 2 - Using ScaleTransform and bitmap offsetY = 120; g.InterpolationMode = InterpolationMode.NearestNeighbor; // Without this, lines appear even but are burry (lines blended together) using (Bitmap bmp = new Bitmap(100, 50)) { using (Graphics g1 = Graphics.FromImage(bmp)) { for (int row = 0; row <= 49; row++) { for (int col = 0; col <= 99; col++) { if (col % 2 == 0) c = Color.SkyBlue; else c = Color.Black; g1.FillRectangle(new SolidBrush(c), col, row, 1, 1); // Same effect using SetPixel(col, row, c) } } } g.DrawImage(bmp, new Rectangle(10, offsetY, bmp.Width, bmp.Height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel); } // Method 3 - Proves it's not a ScaleTransform issue as not using ScaleTransform and doing the scaling manualy offsetY = 220; g.ResetTransform(); for (int row = 0; row <= 49; row++) { for (int col = 0; col <= 99; col++) { if (col % 2 == 0) c = Color.SkyBlue; else c = Color.Black; g.FillRectangle(new SolidBrush(c), System.Convert.ToSingle((col + 10) * (this.Width / (double)150)), System.Convert.ToSingle(row + offsetY), System.Convert.ToSingle(1 * (this.Width / (double)150)), System.Convert.ToSingle(1 * (this.Height / (double)150))); } } base.OnPaint(e); }
已尝试的方案
- 切换多种
InterpolationMode模式 - 先绘制到Bitmap再进行缩放绘制
预期线条宽度保持均匀,但问题仍存在。
解决方案
这个问题的核心是浮点缩放时的像素对齐误差:当缩放比例不是整数倍时,单个逻辑像素被映射到非整数个物理像素,GDI+的默认绘制逻辑会因浮点坐标的取整/对齐规则,导致部分像素块被拉伸、部分被压缩,最终呈现宽窄不均的条纹。以下是几种可行的解决思路:
1. 优先使用整数倍缩放
如果可以控制窗体尺寸,尽量让this.Width / 150的结果为整数(比如将窗体宽度设为150、300、450等),这样每个逻辑像素对应整数个物理像素,从根源上避免对齐误差。
2. 启用GDI+像素对齐优化
在绘制前设置PixelOffsetMode.Half,让绘制的图形偏移半个像素,减少浮点坐标带来的锯齿和宽窄不均:
// 在绘制前添加这行代码 g.PixelOffsetMode = PixelOffsetMode.Half; // 可选:如果可以接受轻微模糊,开启抗锯齿进一步优化 g.SmoothingMode = SmoothingMode.AntiAlias;
如果需要保持绝对清晰,可配合SmoothingMode.None使用,平衡清晰度和线条均匀性。
3. 直接绘制适配目标尺寸的Bitmap
不要先绘制小尺寸Bitmap再缩放,而是直接创建与最终显示尺寸一致的Bitmap,在Bitmap内部完成逻辑到物理坐标的转换:
// 示例:针对Method 2修改的代码 offsetY = 120; float scaleX = (float)(this.Width / 150.0); int targetWidth = (int)(100 * scaleX); int targetHeight = 50; using (Bitmap bmp = new Bitmap(targetWidth, targetHeight)) { using (Graphics g1 = Graphics.FromImage(bmp)) { for (int row = 0; row <= 49; row++) { for (int col = 0; col <= 99; col++) { Color c = col % 2 == 0 ? Color.SkyBlue : Color.Black; // 直接绘制缩放后的矩形,避免二次缩放 g1.FillRectangle(new SolidBrush(c), col * scaleX, row, scaleX, 1); } } } g.DrawImage(bmp, 10, offsetY); }
这种方式让每个逻辑列对应连续的物理像素块,不会出现宽窄差异。
4. 手动计算像素范围,避免浮点截断
在手动缩放时,不要直接用(col +10)*scale作为坐标,而是计算每个逻辑矩形的物理像素起始/结束位置,确保宽度均匀:
// 示例:针对Method 3修改的代码 offsetY = 220; g.ResetTransform(); float scaleX = (float)(this.Width / 150.0); for (int row = 0; row <= 49; row++) { float y = row + offsetY; for (int col = 0; col <= 99; col++) { Color c = col % 2 == 0 ? Color.SkyBlue : Color.Black; // 计算当前列的物理像素起始和结束位置 float xStart = (col + 10) * scaleX; float xEnd = (col + 11 + 10) * scaleX - 0.001f; // 减去极小值避免重叠 float width = xEnd - xStart; g.FillRectangle(new SolidBrush(c), xStart, y, width, 1); } }
这种方式让每个逻辑列的宽度尽可能接近平均缩放值,避免因浮点取整导致的宽度差异。
内容的提问来源于stack exchange,提问作者Bob Aune
相关产品推荐
相关产品推荐

