Title: Trim images in C#
This example shows how you can trim images to remove unnecessary whitespace around their edges.
Sometimes when I make pictures for a book, it's hard to tell where the edges of the picture begin, so I risk either including more white space than necessary or clipping off the edges of shapes that are antialiased near the picture's edges. This example loops over the image's pixels to see where the non-white pixels begin.
The heart of the program is the following TrimImage method.
// Trim the image to its non-white pixels plus a margin.
private Bitmap TrimImage(Bitmap image, int margin)
{
// Make a Bitmap32.
Bitmap32 bm32 = new Bitmap32(image);
bm32.LockBitmap();
// Find the pixel bounds.
Rectangle src_rect = ImageBounds(bm32);
bm32.UnlockBitmap();
// Copy the non-white area.
int wid = src_rect.Width + 2 * margin;
int hgt = src_rect.Height + 2 * margin;
Bitmap bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.Clear(Color.White);
Rectangle dest_rect = new Rectangle(
margin, margin, src_rect.Width, src_rect.Height);
gr.DrawImage(image, dest_rect, src_rect, GraphicsUnit.Pixel);
}
return bm;
}
This method creates a Bitmap32 object and locks it so we can work with it. For information on how that class works, see the post Use the Bitmap32 class to manipulate image pixels very quickly in C#.
The method then calls the ImageBounds method described shortly to find the bounds where the image is non-white and adds a margin if desired. The method then creates a new Bitmap to fit the desired area and makes an associated Graphics object. It clears the bitmap and uses its DrawImage method to copy the desired part of the original image onto the bitmap. The method finishes by returning the new bitmap.
The following code shows the ImageBounds method.
// Get the image's bounds.
private Rectangle ImageBounds(Bitmap32 bm32)
{
// ymin.
int ymin = bm32.Height - 1;
for (int y = 0; y < bm32.Height; y++)
{
if (!RowIsWhite(bm32, y))
{
ymin = y;
break;
}
}
// ymax.
int ymax = 0;
for (int y = bm32.Height - 1; y >= ymin; y--)
{
if (!RowIsWhite(bm32, y))
{
ymax = y;
break;
}
}
// xmin.
int xmin = bm32.Width - 1;
for (int x = 0; x < bm32.Width; x++)
{
if (!ColumnIsWhite(bm32, x))
{
xmin = x;
break;
}
}
// xmax.
int xmax = 0;
for (int x = bm32.Width - 1; x >= xmin; x--)
{
if (!ColumnIsWhite(bm32, x))
{
xmax = x;
break;
}
}
// Build the rectangle.
return new Rectangle(xmin, ymin,
xmax - xmin + 1, ymax - ymin + 1);
}
This method loops through the image's rows starting at row 0 and working its way down through the image. For each row, the method calls RowIsWhite (described next) to see if the row has any non-white pixels. When it finds a non-white row, the method saves its index in variable ymin and then breaks out of its loop.
The method performs similar steps to find the bottommost non-white row. It then uses similar calculations to find the largest and smallest non-white columns.
The following code shows the RowIsWhite method.
// Return true if this row is all white.
private bool RowIsWhite(Bitmap32 bm32, int y)
{
byte r, g, b, a;
for (int x = 0; x < bm32.Width; x++)
{
bm32.GetPixel(x, y, out r, out g, out b, out a);
if ((r != 255) || (g != 255) || (b != 255)) return false;
}
return true;
}
This method loops through the pixels in the image's row with the indicated y coordinate. If any of a pixel's red, green, or blue component is less than 255, the method returns false to indicate that the row is not completely white.
You could modify that test to look for other colors. For example, you could see if the components are less than 230 to skip pixels that are close to but not exactly white. Or you could look for components that are greater than 0 to skip black pixels. (In that case, you should probably change the method's name to something less misleading.)
The ColumnIsWhite method is similar to RowIsWhite except it checks columns instead of rows.
Download the example to experiment with it and to see additional details.
|