C# 是否自动将位图修剪为最小大小?

C# 是否自动将位图修剪为最小大小?,c#,image-processing,bitmap,gdi+,C#,Image Processing,Bitmap,Gdi+,假设我有一个32bpp ARGB模式下的System.Drawing.Bitmap。这是一个大的位图,但是它大部分是完全透明的像素,中间有一个相对较小的图像。 什么是一个快速算法来检测“真实”图像的边界,这样我就可以裁剪掉周围所有的透明像素 或者,在.Net中是否已经有一个函数可以用于此功能?基本思想是检查图像的每个像素,以找到图像的上、左、右和下边界。要有效地执行此操作,请不要使用GetPixel方法,因为该方法非常慢。使用锁位 以下是我提出的实施方案: static Bitmap TrimB

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

什么是一个快速算法来检测“真实”图像的边界,这样我就可以裁剪掉周围所有的透明像素


或者,在.Net中是否已经有一个函数可以用于此功能?

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

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

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;
}
静态位图修剪位图(位图源)
{
矩形srcRect=默认值(矩形);
BitmapData数据=null;
尝试
{
data=source.LockBits(新矩形(0,0,source.Width,source.Height),ImageLockMode.ReadOnly,PixelFormat.Format32bppArgb);
字节[]缓冲区=新字节[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;
对于(int y=0;yxMax)xMax=x;
如果(yyMax)yMax=y;
}
}
}
if(xMax
它可能会被优化,但我不是GDI+专家,所以这是我在没有进一步研究的情况下所能做的最好的


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

  • 从左向右扫描,直到找到不透明像素;将(x,y)存储到(xMin,yMin)
  • 自上而下扫描,直到找到一个不透明像素(仅适用于x>=xMin);将y存储到yMin中
  • 从右向左扫描,直到找到不透明像素(仅适用于y>=yMin);将x存储到xMax中

  • 自下而上扫描,直到找到一个不透明像素(仅适用于xMin我想建议一种分治方法:

  • 在中间分割图像(例如垂直)
  • 检查切割线上是否有不透明像素(如果有,请记住“最小/最大”作为边界框)
  • 再次垂直拆分左半部分
  • 如果剪切线包含不透明像素->更新边界框
  • 如果没有,你可能会丢弃最左边的那一半(我不知道图片)
  • 继续使用左右半部分(您声明图像位于中间的某个位置),直到找到图像的最左侧边界
  • 对右半部分也这样做

  • 截断是直的吗?如果是这样,从L->R和T->B读取像素会非常快。如果是正方形,你可能会节省更多的时间,并从四个边的中心开始进行二进制搜索(至少减少像素查询)小的,嵌入的图像也可以有透明的像素吗?不幸的是,图像可以是任何形状。我看不到比O(n^2)更好的方法我认为你的第五点是错误的:可能有几个不同的区域有不透明的像素,所以在切割线上没有不透明的像素并不意味着有任何问题,但是是的:二进制搜索并不总是适用于我的图像-例如,可能有两个图像被空格隔开。顺便说一句,我只是意识到有一种更简单的方法可以裁剪图像,而无需使用图形:
    返回source.Clone(srrect,source.PixelFormat);
    很好的解决方案,非常有用,但我发现我的图像被剪裁了太多像素。从逻辑上看,你的方法似乎是正确的,但我将调用改为Rectangle.FromLTRB到srrect=Rectangle.FromLTRB(xMin,yMin,xMax+1,yMax+1)现在它工作得很好。谢谢你的代码!
    default(矩形)
    也是错误的。Windows不允许小于一个像素的位图。它会尝试失败,等等。@biterblue,这没关系,因为
    srrect
    的初始值在使用之前总是被覆盖。@thomaslevsque不,它并不总是被覆盖。例如
    data=source.LockBits(…);
    可以引发异常,
    srrect=…
    位于try块的最末尾。就像我拼写错误的(^^^):“如果try失败,等等。”
    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;
    }