自动将位图修剪为最小尺寸?

时间:2011-01-27 18:18:35

标签: c# image-processing bitmap gdi+

假设我在32bpp ARGB模式下有System.Drawing.Bitmap。这是一个很大的位图,但它主要是完全透明的像素,在中间某处有一个相对较小的图像。

检测“真实”图像边框的快速算法是什么,所以我可以裁掉周围的所有透明像素?

或者,我可以使用.Net中的功能吗?

2 个答案:

答案 0 :(得分:25)

基本思想是检查图像的每个像素,以找到图像的上,左,右和下边界。要有效地执行此操作,请不要使用GetPixel方法,这非常慢。请改用LockBits

以下是我提出的实施方案:

static Bitmap TrimBitmap(Bitmap source)
{
    Rectangle srcRect = default(Rectangle);
    BitmapData data = null;
    try
    {
        data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        byte[] buffer = new byte[data.Height * data.Stride];
        Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
        int xMin = int.MaxValue;
        int xMax = 0;
        int yMin = int.MaxValue;
        int yMax = 0;
        for (int y = 0; y < data.Height; y++)
        {
            for (int x = 0; x < data.Width; x++)
            {
                byte alpha = buffer[y * data.Stride + 4 * x + 3];
                if (alpha != 0)
                {
                    if (x < xMin) xMin = x;
                    if (x > xMax) xMax = x;
                    if (y < yMin) yMin = y;
                    if (y > yMax) yMax = y;
                }
            }
        }
        if (xMax < xMin || yMax < yMin)
        {
            // Image is empty...
            return null;
        }
        srcRect = Rectangle.FromLTRB(xMin, yMin, xMax, yMax);
    }
    finally
    {
        if (data != null)
            source.UnlockBits(data);
    }

    Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
    Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
    using (Graphics graphics = Graphics.FromImage(dest))
    {
        graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel);
    }
    return dest;
}

它可能是优化的,但我不是GDI +专家,所以如果没有进一步的研究,这是我能做的最好的......


编辑:实际上,通过不扫描图像的某些部分,有一种简单的方法来优化它:

  1. 从左向右扫描,直到找到不透明的像素;将(x,y)存储到(xMin,yMin)
  2. 从上到下扫描,直到找到不透明的像素(仅适用于x&gt; = xMin);将y存入yMin
  3. 从右向左扫描,直到找到不透明的像素(仅适用于y&gt; = yMin);将x存储到xMax
  4. 从底部到顶部扫描,直到找到不透明的像素(仅适用于xMin&lt; = x&lt; = xMax);将y存入yMax

  5. EDIT2:这是上述方法的实现:

    static Bitmap TrimBitmap(Bitmap source)
    {
        Rectangle srcRect = default(Rectangle);
        BitmapData data = null;
        try
        {
            data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            byte[] buffer = new byte[data.Height * data.Stride];
            Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
    
            int xMin = int.MaxValue,
                xMax = int.MinValue,
                yMin = int.MaxValue,
                yMax = int.MinValue;
    
            bool foundPixel = false;
    
            // Find xMin
            for (int x = 0; x < data.Width; x++)
            {
                bool stop = false;
                for (int y = 0; y < data.Height; y++)
                {
                    byte alpha = buffer[y * data.Stride + 4 * x + 3];
                    if (alpha != 0)
                    {
                        xMin = x;
                        stop = true;
                        foundPixel = true;
                        break;
                    }
                }
                if (stop)
                    break;
            }
    
            // Image is empty...
            if (!foundPixel)
                return null;
    
            // Find yMin
            for (int y = 0; y < data.Height; y++)
            {
                bool stop = false;
                for (int x = xMin; x < data.Width; x++)
                {
                    byte alpha = buffer[y * data.Stride + 4 * x + 3];
                    if (alpha != 0)
                    {
                        yMin = y;
                        stop = true;
                        break;
                    }
                }
                if (stop)
                    break;
            }
    
            // Find xMax
            for (int x = data.Width - 1; x >= xMin; x--)
            {
                bool stop = false;
                for (int y = yMin; y < data.Height; y++)
                {
                    byte alpha = buffer[y * data.Stride + 4 * x + 3];
                    if (alpha != 0)
                    {
                        xMax = x;
                        stop = true;
                        break;
                    }
                }
                if (stop)
                    break;
            }
    
            // Find yMax
            for (int y = data.Height - 1; y >= yMin; y--)
            {
                bool stop = false;
                for (int x = xMin; x <= xMax; x++)
                {
                    byte alpha = buffer[y * data.Stride + 4 * x + 3];
                    if (alpha != 0)
                    {
                        yMax = y;
                        stop = true;
                        break;
                    }
                }
                if (stop)
                    break;
            }
    
            srcRect = Rectangle.FromLTRB(xMin, yMin, xMax, yMax);
        }
        finally
        {
            if (data != null)
                source.UnlockBits(data);
        }
    
        Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
        Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
        using (Graphics graphics = Graphics.FromImage(dest))
        {
            graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel);
        }
        return dest;
    }
    

    如果非透明部分当然很小,则不会有显着的增益,因为它仍会扫描大部分像素。但如果它很大,则只扫描非透明部分周围的矩形。

答案 1 :(得分:1)

我想建议一个分歧&amp;征服方法:

  1. 将图像分割在中间(例如垂直)
  2. 检查切割线上是否有非透明像素(如果是,请记住边界框的最小/最大值)
  3. 再次垂直分割左半部分
  4. 如果切割线包含非透明像素 - >更新边界框
  5. 如果没有,你可以丢弃最左边的一半(我不知道图片)
  6. 继续左右半部分(你说图像位于中间的某个位置),直到找到图像最左边的边界
  7. 为右半部分做同样的事情