查找C#图元文件的最后绘制像素

时间:2018-11-19 18:27:47

标签: c# graphics metafile

我有一个图元文件对象。由于我无法控制的原因,它提供的尺寸要大得多(大数千倍)。

例如,它可能是40 000 x 40 000,但在2000 x 1600的区域中仅包含“真实”(非透明)像素。

最初,该图元文件只是被绘制到控件上,并且控件边界将区域限制为合理的大小。

现在,我尝试根据用户输入将其拆分为不同的动态大小块。我想要做的是计算其中将有多少块(在x和y中,甚至拆分成块的二维网格)。

我知道,从技术上讲,我可以采用O(N²)的方式,并仅逐个检查像素以找到绘制图像的“真实”边界。

但这会非常缓慢。

我正在寻找一种获取整个图元文件中最后绘制的像素的位置(x,y)的方法,而不需要遍历它们中的每一个。

enter image description here

由于DrawImage方法不是很慢,至少不是N²慢,因此我认为图元文件对象的内部进行了一些优化,可以实现类似这样的效果。就像List对象的.Count属性比实际计数对象要快得多,是否有某种方法可以获取图元文件的实际边界?

在这种情况下,绘制的内容将始终为矩形。我可以安全地假设最后一个像素将是相同的,无论我是先按x然后按y,还是按y然后按x循环。

如何找到这个“最后一个”像素的坐标

1 个答案:

答案 0 :(得分:1)

对于如此大的图像,找到不透明像素的边界矩形确实是一个有趣的挑战。

最直接的方法是处理WMF内容,但这也是迄今为止最难解决的问题。

让我们将图像渲染为位图,然后查看该位图。

首先是基本方法,然后进行一些优化。

要获取边界,需要找到l eft, top, rightbottom边界。

这是一个简单的功能:

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);
}

我本可以通过不遍及整个高度来进行优化,而仅遍历我们已经发现的部分(加上增量)。

如果可以使用有关数据的知识,则可以实现进一步的优化。如果我们知道图像数据已已连接,则可以在循环中使用较大的步骤,直到找到像素为止,然后回溯一个步骤。