使用Graphics.DrawImage放大图像时尺寸偏小及裁剪问题求助
问题:使用Graphics.DrawImage放大图像时尺寸不准确
当我使用Graphics.DrawImage绘制图像并将其放大到比原始尺寸更大的尺寸时,最终图像会略小一些。可以看到绘制在图像后方的绿色背景线条没有被完全覆盖:

绿色线条不属于图像本身,本应被图像完全覆盖。
补充信息:
- 我用传入
DrawImage的同一个矩形绘制绿色背景,这个矩形的尺寸就是图像应该有的准确尺寸,所以参数值应该没问题。 - 绿色矩形是用
FillRectangle绘制的,不需要画笔计算,而且我核对过参数值都是正确的,只有图像显示有偏差。 - 附上渲染代码:
// 缩放比例集合 public readonly float[] SCALES = { 0.05f, 0.1f, 0.125f, 0.25f, 0.333f, 0.5f, 0.667f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f, 12.0f, 15.0f, 20.0f, 30.0f, 36.0f }; private int scaleIndex = 8; protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); float ScaleFactor = SCALES[scaleIndex]; e.Graphics.InterpolationMode = ScaleFactor < 1 ? InterpolationMode.Bicubic : InterpolationMode.NearestNeighbor; Image im = Properties.Resources.TSprite0; for (int y = 0; y < TilesVertical; y++) { for (int x = 0; x < TilesHorizontal; x++) { float sx = im.Width * ScaleFactor; float sy = im.Height * ScaleFactor; Point p = new Point((int)(-scrollPosition.X + sx * x), (int)(-scrollPosition.Y + sy * y)); Size s = new Size((int)Math.Floor(sx), (int)Math.Floor(sy)); // 背景绿色矩形应该和图像尺寸一致 e.Graphics.FillRectangle(Brushes.Lime, new Rectangle(p, s)); e.Graphics.DrawImage(im, new Rectangle(p, s), 0, 0, 16, 16, GraphicsUnit.Pixel); } } im.Dispose(); }
- 另外发现图像像是左侧和顶部被裁剪,对比Photoshop放大效果和GDI+渲染效果:

解决方案
嘿,这个问题我之前做GDI+绘图的时候也碰到过,本质是GDI+的像素对齐逻辑加上你代码里的尺寸处理方式导致的,咱们一步步来修复:
1. 别再用Math.Floor截断尺寸了
你现在把缩放后的浮点尺寸用Math.Floor转成整数,这会直接把小数部分砍掉——比如缩放后应该是20.9像素的宽度,直接变成20,自然会比背景矩形小一圈。不如直接用RectangleF来定义目标区域,让GDI+自己处理像素级的对齐,避免手动舍入的误差:
把原来的Point和Size替换成RectangleF,绘制的时候用对应的重载:
// 计算目标区域的浮点矩形,完全保留缩放后的精确尺寸 RectangleF destRect = new RectangleF( -scrollPosition.X + sx * x, -scrollPosition.Y + sy * y, sx, sy ); // 绘制绿色背景用RectangleF版本的FillRectangle e.Graphics.FillRectangle(Brushes.Lime, destRect); // DrawImage也用RectangleF作为目标区域 e.Graphics.DrawImage(im, destRect, 0, 0, 16, 16, GraphicsUnit.Pixel);
2. 调整Graphics的像素偏移模式
GDI+默认的像素对齐方式有时候会“偷偷”偏移半个像素,导致图像看起来像是被裁剪。你可以在OnPaint开头加上这一行,强制更精确的像素对齐:
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
如果这个模式效果不好,也可以试试PixelOffsetMode.None,根据你的实际显示效果调整就行。
3. 明确设置Graphics的单位为像素
虽然默认是像素,但有时候某些场景下单位会被意外修改,明确设置一下更保险:
e.Graphics.PageUnit = GraphicsUnit.Pixel;
4. 用using自动管理图像资源
你现在手动调用im.Dispose(),虽然没问题,但用using语句会更可靠,能自动处理资源释放,避免忘记Dispose导致的内存泄漏:
using (Image im = Properties.Resources.TSprite0) { // 原来的循环代码放这里面 }
整合后的完整代码
把这些修改整合起来,代码大概是这样:
public readonly float[] SCALES = { 0.05f, 0.1f, 0.125f, 0.25f, 0.333f, 0.5f, 0.667f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f, 12.0f, 15.0f, 20.0f, 30.0f, 36.0f }; private int scaleIndex = 8; protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); // 配置Graphics的关键参数 float ScaleFactor = SCALES[scaleIndex]; e.Graphics.PageUnit = GraphicsUnit.Pixel; e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; e.Graphics.InterpolationMode = ScaleFactor < 1 ? InterpolationMode.Bicubic : InterpolationMode.NearestNeighbor; using (Image im = Properties.Resources.TSprite0) { float tileWidth = im.Width * ScaleFactor; float tileHeight = im.Height * ScaleFactor; for (int y = 0; y < TilesVertical; y++) { for (int x = 0; x < TilesHorizontal; x++) { RectangleF destRect = new RectangleF( -scrollPosition.X + tileWidth * x, -scrollPosition.Y + tileHeight * y, tileWidth, tileHeight ); e.Graphics.FillRectangle(Brushes.Lime, destRect); // 这里用im.Width和im.Height代替硬编码的16,以后换图像不用改代码 e.Graphics.DrawImage(im, destRect, 0, 0, im.Width, im.Height, GraphicsUnit.Pixel); } } } }
测试一下这个修改后的代码,应该就能解决图像尺寸不准、边缘露出绿色背景的问题了,而且图像的裁剪感也会消失~
内容的提问来源于stack exchange,提问作者Jonas Kohl
相关产品推荐
相关产品推荐

