【发布时间】:2011-07-06 07:18:36
【问题描述】:
我有一个使用Graphics.DrawImage 拉伸和绘制位图的 Winforms 应用程序,我需要帮助来准确理解源像素如何映射到目标。
理想情况下,我想编写如下函数:
Point MapPixel(Point p, Size src, Size dst)
获取源图像上的像素坐标,并返回缩放后目标上与其对应的“左上”像素的坐标。
为了清楚起见,下面是一个简单的示例,将 2x2 位图缩放为 4x4:
箭头说明了如何将点 (1,0) 输入 MapPixel:
MapPixel(new Point(1, 0), new Size(2, 2), new Size(4, 4))
应该给出 (2,0) 的结果。
让 MapPixel 在上面的例子中工作很简单,使用如下逻辑:
double scaleX = (double)dst.Width / (double)src.Width;
x_dst = (int)Math.Round((double)x_src * scaleX);
但是我注意到当dst.Width 不是src.Width 的偶数倍数时,这种幼稚的实现会由于舍入而产生错误。在这种情况下,DrawImage 需要选择一些像素来绘制比其他像素更大的像素,以使图像适合,而我在复制其逻辑时遇到了麻烦。
以下代码通过将 2x1 位图缩放到几个不同的宽度来演示问题:
Bitmap src = new Bitmap(2, 1);
src.SetPixel(0, 0, Color.Red);
src.SetPixel(1, 0, Color.Blue);
Bitmap[] dst = {
new Bitmap(3, 1),
new Bitmap(5, 1),
new Bitmap(7, 1),
new Bitmap(9, 1)};
// Draw stretched images
foreach (Bitmap b in dst) {
using (Graphics g = Graphics.FromImage(b)) {
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(src, 0, 0, b.Width, b.Height);
}
}
这是原始src 图像和输出dst 图像的样子,以及一些显示 MapPixel 需要如何映射蓝色像素的数字:
我一生都无法弄清楚 DrawImage 是如何决定放大哪个像素的。它似乎有时向上取整,有时向下取整。我不在乎它选择哪个,但我需要它是可预测的,我的功能才能正常工作。
我尝试修改上面的 MapPixel 示例以使用 MidpointRounding.AwayFromZero,甚至将 Math.Round 替换为四舍五入到最接近的 奇数 数的函数(稍微改进了结果,但仍然不是'完美)。我还尝试让 Graphics 类处理缩放 - 即我设置ScaleTransform,调用DrawImageUnscaled,然后尝试使用TransformPoints 转换坐标。有趣的是,TransformPoints 方法的结果并不总是与 DrawImage 和 DrawImageUnscaled 所做的一致。
我也尝试挖掘 GDI+ 以获取提示,但尚未发现任何有用的信息。
我不想仅仅为了保持对它会落在哪里的可预测性而单独绘制每个像素。
如果您想知道,我使用 InterpolationMode.NearestNeighbor 的原因是为了避免抗锯齿(我需要保持单个像素的保真度),并且包含 PixelOffsetMode.Half 因为如果它不存在则 DrawImage将我的位图平移半个像素。
更多问题点的示例包括将 4px 缩放到 13px 时 x=7,以及将 4px 缩放到 17px 时 x=8。
如果需要,我可以发布完整的单元测试代码,让您可以插入并验证 MapPixel 函数。到目前为止,我能够达到 100% 准确度的唯一方法是通过一个丑陋的 hack,它生成一个“提示”源图像,其中每个像素都设置为唯一的颜色。它通过检查提示图像中的颜色来映射坐标,然后在提示图像的缩放版本中查找该颜色。优化是可能的(例如提示图像是单像素宽度或高度,上面的幼稚逻辑用于猜测近似答案并从那里向外工作),但它仍然很难看。
如果有人能阐明 DrawImage 背后的管道并帮助我想出一个更简单(但仍然准确)的 MapPixel 实现,我将不胜感激。
【问题讨论】:
-
是否可以放弃 .DrawImage 并自己进行缩放?显然你不能直接绘制到屏幕上,而是必须通过中间位图,但你会获得 100% 的可预测性! :-)
-
好建议 danbystrom,我确实开始研究那个替代方案。虽然我还是很好奇 DrawImage 是怎么做到的。
-
您是否尝试过检查图形 ctm(使用 Graphics 属性 Transform)?有人会假设它是单位矩阵,但它可能值得检查。如果那里有某种转换,这可能会影响舍入。
标签: bitmap gdi+ scaling drawimage stretchblt