Podstawową ideą jest sprawdzenie każdego piksela obrazu, aby znaleźć górne, lewe, prawe i dolne krawędzie obrazu. Aby to zrobić wydajnie, nie używaj metody GetPixel
, która jest dość powolna. Zamiast tego użyj LockBits
.
Oto realizacja wymyśliłem:
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;
}
może prawdopodobnie być zoptymalizowane, ale nie jestem GDI + ekspertem, więc jest to najlepsze, co mogę zrobić bez dalszych badań ...
EDIT: rzeczywiście, jest to prosty sposób, aby zoptymalizować go, by nie skanuje niektóre części obrazka:
- skanowania lewej do righ t dopóki nie znajdziesz nieprzejrzystego piksela; zapisz (x, y) na (xMin, yMin)
- skanuj od góry do dołu, aż znajdziesz nieprzezroczysty piksel (tylko dla x> = xMin); przechowuj y na yMin
- skanuj od prawej do lewej, aż znajdziesz nieprzezroczysty piksel (tylko dla y> = yMin); przechowuj x na xMax
- skanuj od dołu do góry, aż znajdziesz nieprzezroczysty piksel (tylko dla xMin < = x < = xMax); sklep y do YMAX
Edit2: oto implementacja podejścia powyżej:
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;
}
Nie będzie znaczny zysk, jeśli nieprzezroczysta część jest mały oczywiście od nadal będzie skanował większość pikseli. Ale jeśli jest duży, skanowane będą tylko prostokąty wokół nieprzejrzystej części.
Czy odcięcie jest proste? jeśli tak, odczyt pikseli z L-> R i T-> B działałby bardzo szybko. –
Jeśli jest kwadratowy, prawdopodobnie możesz zaoszczędzić jeszcze więcej czasu i binarnego wyszukiwania od środka na wszystkich 4 stronach (przynajmniej zmniejszając piksele). –
Czy mały, osadzony obraz może zawierać przezroczyste piksele? –