views:

140

answers:

4

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?

+1  A: 

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

DrakeVN
+2  A: 

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.

Gobra
+1  A: 
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.

MusiGenesis
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 http://pastebin.com/He3S8aCH
Echilon
I transposed two variables :P. Problem fixed, expect a blog post soon (will link to it here).
Echilon
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.
MusiGenesis
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).
MusiGenesis
+2  A: 

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);
  }
Chris Taylor