C# - Crop Transparent/White space
Asked Answered
T

7

6

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?

Tenebrific answered 17/7, 2010 at 11:51 Comment(2)
So there ARE other people who dream of continously writing the same line of code over and over again? I thought I was the only one :-)Lailaibach
it would help the community if you details at least one of your approaches and explained how it didn't work.Goodall
T
5
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.

Transformation answered 17/7, 2010 at 12:14 Comment(4)
Thanks, that got me half way there. The only problem is, I also want to resize the image if it's bigger than a specified size. There's more code than will fit in the comments box, but the original image only takes up about half the final image. The code is at pastebin.com/He3S8aCHTenebrific
I transposed two variables :P. Problem fixed, expect a blog post soon (will link to it here).Tenebrific
I think I screwed up, too. The bits that say 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
Also, the code should probably first check the A value to see if the pixel is transparent, and then only check the RGB values if the pixel isn't transparent. Otherwise, the check will stop on any fully transparent (A = 0) non-white pixel (although I doubt this is very common in Bitmaps).Transformation
S
12

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);
  }
Stucco answered 17/7, 2010 at 12:31 Comment(2)
Great solution! I removed || pixelColor.A < 255 to make it work.Prosperus
Similar comment - works almost perfectly, in my case I wanted to just crop all and only the transparent section. I just changed the whole conditional to just " if (pixelColor.A > 0)". Everything else worked the way I wanted at that point.Usm
T
5
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.

Transformation answered 17/7, 2010 at 12:14 Comment(4)
Thanks, that got me half way there. The only problem is, I also want to resize the image if it's bigger than a specified size. There's more code than will fit in the comments box, but the original image only takes up about half the final image. The code is at pastebin.com/He3S8aCHTenebrific
I transposed two variables :P. Problem fixed, expect a blog post soon (will link to it here).Tenebrific
I think I screwed up, too. The bits that say 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
Also, the code should probably first check the A value to see if the pixel is transparent, and then only check the RGB values if the pixel isn't transparent. Otherwise, the check will stop on any fully transparent (A = 0) non-white pixel (although I doubt this is very common in Bitmaps).Transformation
S
2

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

Severance answered 17/7, 2010 at 12:2 Comment(0)
J
2

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.

Joceline answered 17/7, 2010 at 12:4 Comment(0)
D
1

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;
}
Dazzle answered 25/6 at 11:19 Comment(1)
Thanks for this solution 13 years later :DTenebrific
L
0

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

Lactescent answered 28/8, 2017 at 13:26 Comment(0)
S
0

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.

Screen shot of the property in WPF

Since answered 7/2, 2022 at 8:58 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Pentateuch

© 2022 - 2024 — McMap. All rights reserved.