I'm trying to remove all white or transparent pixels from an image, leaving the actual image (cropped). I've tried a few solutions, but none seem to work. Any suggestions or am I going to spend the night writing image cropping code?
public Bitmap CropBitmap(Bitmap original)
{
// determine new left
int newLeft = -1;
for (int x = 0; x < original.Width; x++)
{
for (int y = 0; y < original.Height; y++)
{
Color color = original.GetPixel(x, y);
if ((color.R != 255) || (color.G != 255) || (color.B != 255) ||
(color.A != 0))
{
// this pixel is either not white or not fully transparent
newLeft = x;
break;
}
}
if (newLeft != -1)
{
break;
}
// repeat logic for new right, top and bottom
}
Bitmap ret = new Bitmap(newRight - newLeft, newTop - newBottom);
using (Graphics g = Graphics.FromImage(ret)
{
// copy from the original onto the new, using the new coordinates as
// source coordinates for the original
g.DrawImage(...);
}
return ret
}
Note that this function will be slow as dirt. GetPixel()
is unbelievably slow, and accessing the Width
and Height
properties of a Bitmap
inside a loop is also slow. LockBits
would be the proper way to do this - there are tons of examples here on StackOverflow.
color.R != 0
etc. should actually say color.R != 255
, since a white pixel will have R, G and B values of 255 for each (an RGB of 0,0,0 is black). If the original version worked for you, then it probably only cropped the transparent (and the black) pixels. –
Transformation So, what you want to do is find the top, left most non white/transparent pixel and the bottom, right most non white/transparent pixel. These two coordinates will give you a rectangle that you can then extract.
// Load the bitmap
Bitmap originalBitmap = Bitmap.FromFile("d:\\temp\\test.bmp") as Bitmap;
// Find the min/max non-white/transparent pixels
Point min = new Point(int.MaxValue, int.MaxValue);
Point max = new Point(int.MinValue, int.MinValue);
for (int x = 0; x < originalBitmap.Width; ++x)
{
for (int y = 0; y < originalBitmap.Height; ++y)
{
Color pixelColor = originalBitmap.GetPixel(x, y);
if (!(pixelColor.R == 255 && pixelColor.G == 255 && pixelColor.B == 255)
|| pixelColor.A < 255)
{
if (x < min.X) min.X = x;
if (y < min.Y) min.Y = y;
if (x > max.X) max.X = x;
if (y > max.Y) max.Y = y;
}
}
}
// Create a new bitmap from the crop rectangle
Rectangle cropRectangle = new Rectangle(min.X, min.Y, max.X - min.X, max.Y - min.Y);
Bitmap newBitmap = new Bitmap(cropRectangle.Width, cropRectangle.Height);
using (Graphics g = Graphics.FromImage(newBitmap))
{
g.DrawImage(originalBitmap, 0, 0, cropRectangle, GraphicsUnit.Pixel);
}
|| pixelColor.A < 255
to make it work. –
Prosperus public Bitmap CropBitmap(Bitmap original)
{
// determine new left
int newLeft = -1;
for (int x = 0; x < original.Width; x++)
{
for (int y = 0; y < original.Height; y++)
{
Color color = original.GetPixel(x, y);
if ((color.R != 255) || (color.G != 255) || (color.B != 255) ||
(color.A != 0))
{
// this pixel is either not white or not fully transparent
newLeft = x;
break;
}
}
if (newLeft != -1)
{
break;
}
// repeat logic for new right, top and bottom
}
Bitmap ret = new Bitmap(newRight - newLeft, newTop - newBottom);
using (Graphics g = Graphics.FromImage(ret)
{
// copy from the original onto the new, using the new coordinates as
// source coordinates for the original
g.DrawImage(...);
}
return ret
}
Note that this function will be slow as dirt. GetPixel()
is unbelievably slow, and accessing the Width
and Height
properties of a Bitmap
inside a loop is also slow. LockBits
would be the proper way to do this - there are tons of examples here on StackOverflow.
color.R != 0
etc. should actually say color.R != 255
, since a white pixel will have R, G and B values of 255 for each (an RGB of 0,0,0 is black). If the original version worked for you, then it probably only cropped the transparent (and the black) pixels. –
Transformation In WPF we have a WriteableBitmap class. Is this what are you looking for ? If it is the case please have a look at http://blogs.msdn.com/b/jgalasyn/archive/2008/04/17/using-writeablebitmap-to-display-a-procedural-texture.aspx
Per-pixel check should do the trick. Scan each line to find empty line from the top & bottom, scan each row to find left & right constraints (this can be done in one pass with either rows or columns). When the constraint is found - copy the part of the image to another buffer.
here is an extension method that will loop through only the necessary pixels. Thank you Chris Taylor and MusiGenesis.
/// <summary>
/// @by Chris Taylor and MusiGenesis 2010 @mod Orien 2024
/// <br>Remove non-white/transparent area around image.</br>
/// </summary>
public static Image CropImageWhitespace(this Image sourceBmp) {
// Get abbreviations
var bmp = (Bitmap)sourceBmp;
var w = sourceBmp.Width;
var h = sourceBmp.Height;
// Find the min/max non non-white/transparent pixels
var min = new Point();
var max = new Point(bmp.Width, bmp.Height);
// Lambda expression - if pixel is either not white and not fully transparent
bool isNonWhiteAndOpaquePixel(Color c) => !(c.R == 255 && c.G == 255 && c.B == 255) && c.A > 0;
// get min left non non-white/transparent pixel (scan column and progress from left to right)
bool exit = false;
for (int row = 0; row < w && !exit; row++) {
for (int column = 0; column < h && !exit; column++) {
if (isNonWhiteAndOpaquePixel(bmp.GetPixel(row, column))) {
min.X = row; exit = true; break;
}
}
}
// get min top non non-white/transparent pixel (scan row and progress from top to bottom)
exit = false;
for (int column = 0; column < h && !exit; column++) {
for (int row = 0; row < w && !exit; row++) {
if (isNonWhiteAndOpaquePixel(bmp.GetPixel(row, column))) {
min.Y = column; exit = true; break;
}
}
}
// get max right non non-white/transparent pixel (scan column and progress from right to left)
exit = false;
for (int row = w-1; row >= 0 && !exit; row--) {
for (int column = h-1; column >= 0 && !exit; column--) {
if (isNonWhiteAndOpaquePixel(bmp.GetPixel(row, column))) {
max.X = row; exit = true; break;
}
}
}
// get max bottom non-white/transparent pixel (scan row and progress from bottom to top)
exit = false;
for (int column = h-1; column >= 0 && !exit; column--) {
for (int row = w-1; row >= 0 && !exit; row--) {
if (isNonWhiteAndOpaquePixel(bmp.GetPixel(row, column))) {
max.Y = column; exit = true; break;
}
}
}
// Create a new bitmap from the crop rectangle
var cropRectangle = new Rectangle(min.X, min.Y, max.X - min.X, max.Y - min.Y);
var b = new Rectangle(0, 0, sourceBmp.Width, sourceBmp.Height);
Console.WriteLine("ImageExtensions > CropImageTransparentArea > rect:{0} cropedRect:{1}", b, cropRectangle);
var croppedBmp = new Bitmap(cropRectangle.Width, cropRectangle.Height);
using (Graphics g = Graphics.FromImage(croppedBmp)) {
g.DrawImage(sourceBmp, 0, 0, cropRectangle, GraphicsUnit.Pixel);
}
return croppedBmp;
}
I found a method to batch trim a few thousand .jpg files in about 10 minutes, but I didn't do it in code. I used the Convert feature of Snag-It Editor. I don't know if this is an option for you, if you need to do this trimming once or your need is ongoing, but for the price of the software, which isn't a whole lot, I considered this a decent workaround. (I do not work for or represent Techsmith.)
Joey
Adding to this, if you are in WPF and you have excess space around your image, check the properties of the image and make sure your Stretch property is set to fill. This eliminated the space around the image.
© 2022 - 2024 — McMap. All rights reserved.