問題描述
假設我有一個 32bpp ARGB 模式的 System.Drawing.Bitmap
.這是一個大的位圖,但它主要是完全透明的像素,中間有一個相對較小的圖像.
Suppose I have a System.Drawing.Bitmap
in 32bpp ARGB mode. It's a large bitmap, but it's mostly fully transparent pixels with a relatively small image somewhere in the middle.
檢測真實"圖像邊界的快速算法是什么,這樣我就可以剪掉它周圍的所有透明像素?
What is a fast algorithm to detect the borders of the "real" image, so I can crop away all the transparent pixels from around it?
或者,.Net 中是否已經(jīng)有一個函數(shù)可以用于此目的?
Alternatively, is there a function already in .Net that I can use for this?
推薦答案
基本思想是檢查圖像的每個像素點,找到圖像的上、左、右、下邊界.要有效地執(zhí)行此操作,請不要使用速度很慢的 GetPixel
方法.改用 LockBits
.
The basic idea is to check every pixel of the image to find the top, left, right and bottom bounds of the image. To do this efficiently, don't use the GetPixel
method, which is pretty slow. Use LockBits
instead.
這是我想出的實現(xiàn):
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;
}
它可能可以優(yōu)化,但我不是 GDI+ 專家,所以這是我能做的最好的,無需進一步研究...
It can probably be optimized, but I'm not a GDI+ expert, so it's the best I can do without further research...
實際上,有一種簡單的方法可以優(yōu)化它,即不掃描圖像的某些部分:
actually, there's a simple way to optimize it, by not scanning some parts of the image :
- 從左到右掃描,直到找到一個不透明的像素;將 (x, y) 存入 (xMin, yMin)
- 從上到下掃描,直到找到一個不透明的像素(僅適用于 x >= xMin);將 y 存入 yMin
- 從右向左掃描,直到找到一個不透明的像素(僅適用于 y >= yMin);將 x 存入 xMax
- 從下往上掃描,直到找到一個不透明的像素(僅適用于 xMin <= x <= xMax);將 y 存入 yMax
<小時>
這是上述方法的實現(xiàn):
here's an implementation of the approach above:
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;
}
當然,如果非透明部分很小,則不會有明顯的增益,因為它仍然會掃描大部分像素.但如果它很大,則只會掃描不透明部分周圍的矩形.
There won't be a significant gain if the non-transparent part is small of course, since it will still scan most of the pixels. But if it's big, only the rectangles around the non-transparent part will be scanned.
這篇關(guān)于自動將位圖修剪到最小尺寸?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!