我有一个图元文件对象。由于我无法控制的原因,它提供的尺寸要大得多(大数千倍)。
例如,它可能是40 000 x 40 000,但在2000 x 1600的区域中仅包含“真实”(非透明)像素。
最初,该图元文件只是被绘制到控件上,并且控件边界将区域限制为合理的大小。
现在,我尝试根据用户输入将其拆分为不同的动态大小块。我想要做的是计算其中将有多少块(在x和y中,甚至拆分成块的二维网格)。
我知道,从技术上讲,我可以采用O(N²)的方式,并仅逐个检查像素以找到绘制图像的“真实”边界。
但这会非常缓慢。
我正在寻找一种获取整个图元文件中最后绘制的像素的位置(x,y)的方法,而不需要遍历它们中的每一个。
由于DrawImage方法不是很慢,至少不是N²慢,因此我认为图元文件对象的内部进行了一些优化,可以实现类似这样的效果。就像List对象的.Count属性比实际计数对象要快得多,是否有某种方法可以获取图元文件的实际边界?
在这种情况下,绘制的内容将始终为矩形。我可以安全地假设最后一个像素将是相同的,无论我是先按x然后按y,还是按y然后按x循环。
如何找到这个“最后一个”像素的坐标?
答案 0 :(得分:1)
对于如此大的图像,找到不透明像素的边界矩形确实是一个有趣的挑战。
最直接的方法是处理WMF内容,但这也是迄今为止最难解决的问题。
让我们将图像渲染为位图,然后查看该位图。
首先是基本方法,然后进行一些优化。
要获取边界,需要找到l eft, top, right
和bottom
边界。
这是一个简单的功能:
Rectangle getBounds(Bitmap bmp)
{
int l, r, t, b; l = t = r = b = 0;
for (int x = 0; x < bmp.Width - 1; x++)
for (int y = 0; y < bmp.Height - 1; y++)
if (bmp.GetPixel(x,y).A > 0) { l = x; goto l1; }
l1:
for (int x = bmp.Width - 1; x > l ; x--)
for (int y = 0; y < bmp.Height - 1; y++)
if (bmp.GetPixel(x,y).A > 0) { r = x; goto l2; }
l2:
for (int y = 0; y < bmp.Height - 1; y++)
for (int x = l; x < r; x++)
if (bmp.GetPixel(x,y).A > 0) { t = y; goto l3; }
l3:
for (int y = bmp.Height - 1; y > t; y--)
for (int x = l; x < r; x++)
if (bmp.GetPixel(x,y).A > 0) { b = y; goto l4; }
l4:
return Rectangle.FromLTRB(l,t,r,b);
}
请注意,这将对最后一个垂直循环进行一些优化,以仅查看水平循环尚未测试的部分。
它使用GetPixel
,这很慢。但即使Lockbits
也只能获得大约10倍左右的“收益”。因此,我们需要减少数量。我们仍然需要这样做,因为40k x 40k像素对于Bitmap
来说太大了。
由于WMF通常填充有矢量数据,因此我们可以将其缩小很多。这是一个示例:
string fn = "D:\\_test18b.emf";
Image img = Image.FromFile(fn);
int w = img.Width;
int h = img.Height;
float scale = 100;
Rectangle rScaled = Rectangle.Empty;
using (Bitmap bmp = new Bitmap((int)(w / scale), (int)(h / scale)))
using (Graphics g = Graphics.FromImage(bmp))
{
g.ScaleTransform(1f/scale, 1f/scale);
g.Clear(Color.Transparent);
g.DrawImage(img, 0, 0);
rScaled = getBounds(bmp);
Rectangle rUnscaled = Rectangle.Round(
new RectangleF(rScaled.Left * scale, rScaled.Top * scale,
rScaled.Width * scale, rScaled.Height * scale ));
}
请注意,要正确绘制wmf文件,可能需要调整分辨率。这是我用于测试的示例:
using (Graphics g2 = pictureBox.CreateGraphics())
{
float scaleX = g2.DpiX / img.HorizontalResolution / scale;
float scaleY = g2.DpiY / img.VerticalResolution / scale;
g2.ScaleTransform(scaleX, scaleY);
g2.DrawImage(img, 0, 0); // draw the original emf image.. (*)
g2.ResetTransform();
// g2.DrawImage(bmp, 0, 0); // .. it will look the same as (*)
g2.DrawRectangle(Pens.Black, rScaled);
}
我省略了这个,但是为了完全控制渲染,它也应该包含在上面的代码段中。
根据所需的精度,这可能不够好。
要完美地测量边界,可以做到这一点:使用按比例缩小的测试中的边界并测量未按比例缩放的东西,但四个边界数字周围只有一条小条纹。创建渲染位图时,我们会相应地移动原点。
右边界示例:
Rectangle rScaled2 = Rectangle.Empty;
int delta = 80;
int right = (int)(rScaled.Right * scale);
using (Bitmap bmp = new Bitmap((int)(delta * 2 ), (int)(h )))
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Transparent);
g.DrawImage(img, - right - delta, 0);
rScaled2 = getBounds(bmp);
}
我本可以通过不遍及整个高度来进行优化,而仅遍历我们已经发现的部分(加上增量)。
如果可以使用有关数据的知识,则可以实现进一步的优化。如果我们知道图像数据已已连接,则可以在循环中使用较大的步骤,直到找到像素为止,然后回溯一个步骤。