假设我有一个32bpp ARGB模式的System.Drawing.Bitmap。这是一个大位图,但它主要是完全透明的像素,而实际图像只占据其中较小的一部分。
有什么快速算法可以检测“真实”图像的边界,以便我可以裁剪掉周围所有的透明像素吗?
或者,.Net中是否已经有可用于此的函数?
有什么快速算法可以检测“真实”图像的边界,以便我可以裁剪掉周围所有的透明像素吗?
或者,.Net中是否已经有可用于此的函数?
基本思路是检查图像的每个像素,以找到图像的顶部、左侧、右侧和底部边界。为了高效地完成这个任务,不要使用 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+专家,所以在继续研究之前,这已经是我最好的了...
编辑:实际上,有一种简单的方法可以进行优化,即不扫描图像的某些部分:
编辑2:以下是上述方法的实现:
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;
}
当然,如果非透明部分很小,那么不会有显着的增益,因为它仍将扫描大部分像素。但是,如果非透明部分很大,只有围绕非透明部分的矩形将被扫描。
return source.Clone(srcRect, source.PixelFormat);
- Thomas Levesquedefault(Rectangle)
也是错误的。Windows不允许少于一个像素的位图。它会尝试失败等。 - Bitterbluedata = source.LockBits(...);
可能会抛出异常,而 srcRect = ...
则位于 try 块的最后。就像我拼错的那样(^^):“如果 try 失败等”。 - Bitterbluetry
块中抛出异常,则将执行finally
块,并且异常将向上传播到堆栈。永远不会到达使用srcRect
的地方。 - Thomas Levesque我想建议采用分治法的方法: