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

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

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

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

基本思想是检查图像的每个像素以找到图像的顶部,左侧,右侧和底部边界。 要有效地执行此操作,请不要使用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> = xMin); 将y存入yMin
  3. 从右到左扫描,直到找到不透明的像素(仅适用于y> = yMin); 将x存储到xMax中
  4. 从底部到顶部扫描,直到找到不透明的像素(仅适用于xMin <= x <= xMax); 将y存入yMax

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