Search code examples
c#.netbitmapgdi+gdi

Trimming Bitmap Causes Cutting of Text at Bottom


I'm using the following code to remove whitespace around an image.

  static Bitmap TrimBitmap(Bitmap source)
        {
            Rectangle srcRect = default(Rectangle);
            BitmapData data = null;
            try
            {
                data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                byte[] buffer = new byte[data.Height * data.Stride];
                Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);

                int xMin = int.MaxValue,
                    xMax = int.MinValue,
                    yMin = int.MaxValue,
                    yMax = int.MinValue;

                bool foundPixel = false;

                // Find xMin
                for (int x = 0; x < data.Width; x++)
                {
                    bool stop = false;
                    for (int y = 0; y < data.Height; y++)
                    {
                        byte alpha = buffer[y * data.Stride + 4 * x + 3];
                        if (alpha != 0)
                        {
                            xMin = x;
                            stop = true;
                            foundPixel = true;
                            break;
                        }
                    }
                    if (stop)
                        break;
                }

                // Image is empty...
                if (!foundPixel)
                    return null;

                // Find yMin
                for (int y = 0; y < data.Height; y++)
                {
                    bool stop = false;
                    for (int x = xMin; x < data.Width; x++)
                    {
                        byte alpha = buffer[y * data.Stride + 4 * x + 3];
                        if (alpha != 0)
                        {
                            yMin = y;
                            stop = true;
                            break;
                        }
                    }
                    if (stop)
                        break;
                }

                // Find xMax
                for (int x = data.Width - 1; x >= xMin; x--)
                {
                    bool stop = false;
                    for (int y = yMin; y < data.Height; y++)
                    {
                        byte alpha = buffer[y * data.Stride + 4 * x + 3];
                        if (alpha != 0)
                        {
                            xMax = x;
                            stop = true;
                            break;
                        }
                    }
                    if (stop)
                        break;
                }

                // Find yMax
                for (int y = data.Height - 1; y >= yMin; y--)
                {
                    bool stop = false;
                    for (int x = xMin; x <= xMax; x++)
                    {
                        byte alpha = buffer[y * data.Stride + 4 * x + 3];
                        if (alpha != 0)
                        {
                            yMax = y;
                            stop = true;
                            break;
                        }
                    }
                    if (stop)
                        break;
                }
                srcRect = Rectangle.FromLTRB(xMin, yMin, xMax , yMax);
            }
            finally
            {
                if (data != null)
                    source.UnlockBits(data);
            }

            Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
            Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
            using (Graphics graphics = Graphics.FromImage(dest))
            {
                graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel);
            }
            return dest;
        }

I'm trying to trim a Bitmap with Text Drawn on it.The Proper Image should look like this after trimming

enter image description here

But after trimming i get the following result ..the bottom portion clipped off

What i'm i doing wrong? Please advice..

enter image description here


Solution

  • This is actually a problem with Rectangle.FromLTRB !

    Looking closer at the images you will find that you have actually lost only one row of pixels. (The strong magnification had me fooled for a while..)

    The algorithm to determine the Height (and Width) of the rectangle is basically right, but off by one.

    If you use this

    srcRect = Rectangle.FromLTRB(xMin, yMin, xMax + 1 , yMax + 1);
    

    or this:

    srcRect = new Rectangle(xMin, yMin, xMax - xMin + 1 , yMax - yMin + 1);
    

    it should work.

    You can test with pen and paper: Say: 1st pixel row with a color = 4, first from bottom on a 10 pixel square: 8, that makes 5 not 4 net data: 4,5,6,7,8.

    Do note that this issue is inherent in FromLTRB:

    Rectangle myRectangle = Rectangle.FromLTRB(0, 0, 10, 10);
    

    ..results in a Rectangle with Height=10 even though 0..10 should cover 11 pixels rows! So the Right-Bottom coordinate is actually excluded from the result!!

    I think the whole issue with rectangles being off by one stems from legacy ways to use a Pen with its alignment. When using the same rectangles to fill with a Brush all works as expected.