自动将位图修剪为最小尺寸?
假设我有一个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 +专家,所以如果没有进一步的研究,这是我能做的最好的...
编辑:实际上,通过不扫描图像的某些部分,有一种简单的方法来优化它:
- 从左到右扫描,直到找到不透明的像素; 将(x,y)存储到(xMin,yMin)
- 从上到下扫描,直到找到不透明的像素(仅适用于x> = xMin); 将y存入yMin
- 从右到左扫描,直到找到不透明的像素(仅适用于y> = yMin); 将x存储到xMax中
- 从底部到顶部扫描,直到找到不透明的像素(仅适用于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; }
如果非透明部分当然很小,则不会有显着的增益,因为它仍将扫描大部分像素。 但如果它很大,则只扫描非透明部分周围的矩形。
我想建议一种分而治之的方法:
- 将图像分割在中间(例如垂直)
- 检查切割线上是否有非透明像素(如果是,请记住边界框的最小/最大值)
- 再次垂直分开左半部分
- 如果切割线包含非透明像素 – >更新边界框
- 如果没有,你可以丢弃最左边的一半(我不知道图片)
- 继续左右半部分(你说图像在中间的某个地方),直到找到图像的最左边界
- 为右半边做同样的事