C# Crop white space from around the image

This code will crop the image based on all white and transparent pixels from around the image

cropwhitespacecropwhitespace2

public static Bitmap CropWhiteSpace(Bitmap bmp)
{
  int w = bmp.Width;
  int h = bmp.Height;
  int white = 0xffffff;
 
  Func<int, bool> allWhiteRow = r =>
  {
    for (int i = 0; i < w; ++i)
      if ((bmp.GetPixel(i, r).ToArgb() & white) != white)
        return false;
    return true;
  };
 
  Func<int, bool> allWhiteColumn = c =>
  {
    for (int i = 0; i < h; ++i)
      if ((bmp.GetPixel(c, i).ToArgb() & white) != white)
        return false;
    return true;
  };
 
  int topmost = 0;
  for (int row = 0; row < h; ++row)
  {
    if (!allWhiteRow(row))
      break;
    topmost = row;
  }
 
  int bottommost = 0;
  for (int row = h - 1; row >= 0; --row)
  {
    if (!allWhiteRow(row))
      break;
    bottommost = row;
  }
 
  int leftmost = 0, rightmost = 0;
  for (int col = 0; col < w; ++col)
  {
    if (!allWhiteColumn(col))
      break;
    leftmost = col;
  }
 
  for (int col = w - 1; col >= 0; --col)
  {
    if (!allWhiteColumn(col))
      break;
    rightmost = col;
  }
 
  if (rightmost == 0) rightmost = w; // As reached left
  if (bottommost == 0) bottommost = h; // As reached top.
 
  int croppedWidth = rightmost - leftmost;
  int croppedHeight = bottommost - topmost;
 
  if (croppedWidth == 0) // No border on left or right
  {
    leftmost = 0;
    croppedWidth = w;
  }
 
  if (croppedHeight == 0) // No border on top or bottom
  {
    topmost = 0;
    croppedHeight = h;
  }
 
  try
  {
    var target = new Bitmap(croppedWidth, croppedHeight);
    using (Graphics g = Graphics.FromImage(target))
    {
      g.DrawImage(bmp,
        new RectangleF(0, 0, croppedWidth, croppedHeight),
        new RectangleF(leftmost, topmost, croppedWidth, croppedHeight),
        GraphicsUnit.Pixel);
    }
    return target;
  }
  catch (Exception ex)
  {
    throw new Exception(
      string.Format("Values are topmost={0} btm={1} left={2} right={3} croppedWidth={4} croppedHeight={5}", topmost, bottommost, leftmost, rightmost, croppedWidth, croppedHeight),
      ex);
  }
}
[Test]
public void Test()
{
  var inputPath = "image.png";
  var outputPath = inputPath.Replace(".png", "-out.png");
 
  var bitmap = new Bitmap(inputPath);
  var cropped = CropWhiteSpace(bitmap);
  cropped.Save(outputPath, ImageFormat.Png); 
}

Comments

  1. I see that this procedure doesn't work if the image is not a rectangle, is it true?
    So, why don't look only for the first not white pixel (from beginning of the bitmap) and the last one (from the end)?
    From their coordinates you can simply calculate the new rectangle avoiding a lot of code!

    ReplyDelete
  2. Hi Tedebus, this works fine for any shape. It finds the first non-white pixel from top/bottom/left/right and crops the image based on this information.

    ReplyDelete
  3. Hi!
    I tried your code and I saw that irregular images still contain white areas (like you show on your article, now).
    If your idea is to create a PNG (that has alpha information inside) you may prefer a different approach: bitmap reconstruction based on transparent color substitution on external white areas and, then, image crop.
    This may be an example of code that does it
    (please note that it crop not only white color, but any bounding color)

    public static System.Drawing.Bitmap ClearBitmapBorders(System.Drawing.Bitmap bitmap, int tollerance)
    {
    Color pixelColor;
    Color referenceColor = bitmap.GetPixel(0, 0);
    int X1 = bitmap.Width;
    int Y1 = bitmap.Height;
    int X2 = 0;
    int Y2 = 0;
    int row, column = 0;
    for (row = 0; row < bitmap.Height; row++)
    {
    for (column = 0; column < bitmap.Width; column++)
    {
    pixelColor = bitmap.GetPixel(column, row);
    if ((pixelColor.ToArgb() << 8 == referenceColor.ToArgb() << 8))
    { bitmap.SetPixel(column, row, Color.Transparent); }
    else
    {
    X1 = column < X1 ? column : X1;
    Y1 = row < Y1 ? row : Y1;
    break;
    }
    }
    for (column = bitmap.Width - 1; column >= 0; column--)
    {
    pixelColor = bitmap.GetPixel(column, row);
    if ((pixelColor.ToArgb() << 8 == referenceColor.ToArgb() << 8))
    { bitmap.SetPixel(column, row, Color.Transparent); }
    else
    {
    X2 = column > X2 ? column : X2;
    System.Diagnostics.Debug.WriteLine(X2);
    Y2 = row > Y2 ? row : Y2;
    break;
    }
    }
    }
    try
    {
    var target = new Bitmap(X2, Y2);
    using (Graphics g = Graphics.FromImage(target))
    {
    g.DrawImage(bitmap,
    new RectangleF(0, 0, X2, Y2),
    new RectangleF(X1, Y1, X2, Y2),
    GraphicsUnit.Pixel);
    }
    return target;
    }
    catch
    { return null; }
    }

    If you try it on your image you can see that now only red area will be kept and it remains white inside.

    I hope it should be useful

    ReplyDelete
  4. PS "tollerance" is not used, I posted a piece of code that I used somewhere else and I forgot to delete it.

    ReplyDelete
  5. Hi, just to explain, the purpose of this code was to remove unnecessary white space from around the image to make the image boundaries (square) close to the actual image so I can send that image in the email. I've used it after creating image from html template described here: C# Convert html page to image You're trying to go a bit further to make the remaining space outside the actual image transparent. And yes I've uploaded an extra image to show how it works for non square images.

    ReplyDelete
  6. And thanks for positing your code!

    ReplyDelete
  7. Ok! I understand now...
    The matter was that I found your article on Google and I was looking especially for "C# Crop white space from around the image" (there was a discussion with my colleague) and I saw it. But I saw that it wasn't the answer to my question so I decided to write to you and explain my code.

    ReplyDelete

  8. It works good guys.


    public void TrimImage() {
    int threshhold = 250;


    int topOffset = 0;
    int bottomOffset = 0;
    int leftOffset = 0;
    int rightOffset = 0;
    Bitmap img = new Bitmap(@"e:\Temp\Trim_Blank_Image.png");


    bool foundColor = false;
    // Get left bounds to crop
    for (int x = 1; x < img.Width && foundColor == false; x++)
    {
    for (int y = 1; y < img.Height && foundColor == false; y++)
    {
    Color color = img.GetPixel(x, y);
    if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
    foundColor = true;
    }
    leftOffset += 1;
    }


    foundColor = false;
    // Get top bounds to crop
    for (int y = 1; y < img.Height && foundColor == false; y++)
    {
    for (int x = 1; x < img.Width && foundColor == false; x++)
    {
    Color color = img.GetPixel(x, y);
    if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
    foundColor = true;
    }
    topOffset += 1;
    }


    foundColor = false;
    // Get right bounds to crop
    for (int x = img.Width - 1; x >= 1 && foundColor == false; x--)
    {
    for (int y = 1; y < img.Height && foundColor == false; y++)
    {
    Color color = img.GetPixel(x, y);
    if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
    foundColor = true;
    }
    rightOffset += 1;
    }


    foundColor = false;
    // Get bottom bounds to crop
    for (int y = img.Height - 1; y >= 1 && foundColor == false; y--)
    {
    for (int x = 1; x < img.Width && foundColor == false; x++)
    {
    Color color = img.GetPixel(x, y);
    if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
    foundColor = true;
    }
    bottomOffset += 1;
    }


    // Create a new image set to the size of the original minus the white space
    //Bitmap newImg = new Bitmap(img.Width - leftOffset - rightOffset, img.Height - topOffset - bottomOffset);

    Bitmap croppedBitmap = new Bitmap(img);
    croppedBitmap = croppedBitmap.Clone(
    new Rectangle(leftOffset - 3, topOffset - 3, img.Width - leftOffset - rightOffset + 6, img.Height - topOffset - bottomOffset + 6),
    System.Drawing.Imaging.PixelFormat.DontCare);


    // Get a graphics object for the new bitmap, and draw the original bitmap onto it, offsetting it do remove the whitespace
    //Graphics g = Graphics.FromImage(croppedBitmap);
    //g.DrawImage(img, 1 - leftOffset, 1 - rightOffset);
    croppedBitmap.Save(@"e:\Temp\Trim_Blank_Image-crop.png", ImageFormat.Png);
    }

    ReplyDelete

Post a Comment

Popular posts from this blog

Parse XML to dynamic object in C#