如何使用带有位图的LockBits扫描白色像素,然后将所有非白色像素写入新的位图?

时间:2015-05-26 05:49:24

标签: c# .net winforms

这就是我在form1构造函数中所做的:

Bitmap bmp2 = new Bitmap(@"e:\result1001.jpg");
CropImageWhiteAreas.ImageTrim(bmp2);
bmp2.Save(@"e:\result1002.jpg");
bmp2.Dispose();

CropImageWhiteAreas类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Test
{
    class CropImageWhiteAreas
    {

        public static Bitmap ImageTrim(Bitmap img)
        {
            //get image data
            BitmapData bd = img.LockBits(new Rectangle(Point.Empty, img.Size),
            ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            int[] rgbValues = new int[img.Height * img.Width];
            Marshal.Copy(bd.Scan0, rgbValues, 0, rgbValues.Length);
            img.UnlockBits(bd);


            #region determine bounds
            int left = bd.Width;
            int top = bd.Height;
            int right = 0;
            int bottom = 0;

            //determine top
            for (int i = 0; i < rgbValues.Length; i++)
            {
                int color = rgbValues[i] & 0xffffff;
                if (color != 0xffffff)
                {
                    int r = i / bd.Width;
                    int c = i % bd.Width;

                    if (left > c)
                    {
                        left = c;
                    }
                    if (right < c)
                    {
                        right = c;
                    }
                    bottom = r;
                    top = r;
                    break;
                }
            }

            //determine bottom
            for (int i = rgbValues.Length - 1; i >= 0; i--)
            {
                int color = rgbValues[i] & 0xffffff;
                if (color != 0xffffff)
                {
                    int r = i / bd.Width;
                    int c = i % bd.Width;

                    if (left > c)
                    {
                        left = c;
                    }
                    if (right < c)
                    {
                        right = c;
                    }
                    bottom = r;
                    break;
                }
            }

            if (bottom > top)
            {
                for (int r = top + 1; r < bottom; r++)
                {
                    //determine left
                    for (int c = 0; c < left; c++)
                    {
                        int color = rgbValues[r * bd.Width + c] & 0xffffff;
                        if (color != 0xffffff)
                        {
                            if (left > c)
                            {
                                left = c;
                                break;
                            }
                        }
                    }

                    //determine right
                    for (int c = bd.Width - 1; c > right; c--)
                    {
                        int color = rgbValues[r * bd.Width + c] & 0xffffff;
                        if (color != 0xffffff)
                        {
                            if (right < c)
                            {
                                right = c;
                                break;
                            }
                        }
                    }
                }
            }

            int width = right - left + 1;
            int height = bottom - top + 1;
            #endregion

            //copy image data
            int[] imgData = new int[width * height];
            for (int r = top; r <= bottom; r++)
            {
                Array.Copy(rgbValues, r * bd.Width + left, imgData, (r - top) * width, width);
            }

            //create new image
            Bitmap newImage = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            BitmapData nbd
                = newImage.LockBits(new Rectangle(0, 0, width, height),
                    ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
            Marshal.Copy(imgData, 0, nbd.Scan0, imgData.Length);
            newImage.UnlockBits(nbd);

            return newImage;
        }

    }
}
我也在彼得解决之前尝试过。 两个结果都是(这是我上传图片后的facebook的截图)仍然是周围的白色区域:

Facebook Screenshot

你可以在刚刚上传的图像周围看到矩形,看看周围的白色区域是什么意思。

1 个答案:

答案 0 :(得分:1)

如果我理解正确,您找到了一个使用LockBits()的示例代码段,但您不确定它是如何工作的,或者如何修改它以满足您的特定需求。所以我会尝试从这个角度回答。

首先,一个疯狂的猜测(因为你没有包含你在第一个例子中使用的LockBitmap类的实现):LockBitmap类是某种类型的helper类,它应该封装调用LockBits()并使用结果的工作,包括提供GetPixel()SetPixel()的版本,这些版本可能比在{{1}上调用这些方法快得多直接对象(即访问通过调用Bitmap获得的缓冲区)。

如果是这样,那么修改第一个例子以满足您的需求可能是最好的:

LockBits()

简而言之:将当前像素值复制到局部变量,将该值与public void Change(Bitmap bmp) { Bitmap newBitmap = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat); LockBitmap source = new LockBitmap(bmp), target = new LockBitmap(newBitmap); source.LockBits(); target.LockBits(); Color white = Color.FromArgb(255, 255, 255, 255); for (int y = 0; y < source.Height; y++) { for (int x = 0; x < source.Width; x++) { Color old = source.GetPixel(x, y); if (old != white) { target.SetPixel(x, y, old); } } } source.UnlockBits(); target.UnlockBits(); newBitmap.Save("d:\\result.png"); } 颜色值进行比较,如果它相同,请继续复制像素值到新位图。


第二个代码示例的一些变体也应该起作用。第二个代码示例明确地实现了(我已经假设)封装在第一个代码示例使用的white类中的内容。如果由于某种原因,第一种方法不适合您的需要,您可以按照第二个例子。

在您提供的代码示例中,大部分方法只是处理&#34; grunt work&#34;锁定位图以便可以访问原始数据,然后迭代原始数据。

它根据外部{计算LockBitmapoRow数组偏移量(以&#34;旧行&#34;以及&#34;新行&#34;,我推测)的名称计算{1}}循环,然后通过基于内部nRow循环计算给定行内的偏移量来访问单个像素数据。

由于你想要做的事情基本相同,但是你不想将图像转换为灰度,而只是想要选择性地将所有非白色像素复制到新的位图,你可以(应该可以)简单地修改身体内部y循环。例如:

x

以上内容将完全取代内部x循环的主体。


需要注意的是:请注意,使用byte red = oRow[x * pixelSize + 2], green = oRow[x * pixelSize + 1], blue = oRow[x * pixelSize]; if (red != 255 || green != 255 || blue != 255) { nRow[x * pixelSize + 2] = red; nRow[x * pixelSize + 1] = green; nRow[x * pixelSize] = blue; } 方法时,了解位图的像素格式至关重要。您显示的示例假定位图采用24 bpp格式。如果您自己的位图采用这种格式,那么您就不需要更改任何内容。但如果它们采用不同的格式,您需要调整代码以适应这种情况。例如,如果您的位图采用32 bpp格式,则需要将正确的x值传递给LockBits()方法调用,然后将PixelFormat设置为LockBits()而不是pixelSize正如代码现在所做的那样4


修改

您已指出要剪裁新图像,使其成为包含所有非白色像素所需的最小尺寸。以上是第一个应该实现的示例:

3

此示例包括锁定原始位图后的初始扫描,以查找任何非白色像素的最小和最大坐标值。完成后,它使用该扫描的结果来确定新位图的尺寸。复制像素时,它会将public void Change(Bitmap bmp) { LockBitmap source = new LockBitmap(bmp); source.LockBits(); Color white = Color.FromArgb(255, 255, 255, 255); int minX = int.MaxValue, maxX = int.MinValue, minY = int.MaxValue, maxY = int.MinValue; // Brute-force scan of the bitmap to find image boundary for (int y = 0; y < source.Height; y++) { for (int x = 0; x < source.Width; x++) { if (source.GetPixel(x, y) != white) { if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y; } } } Bitmap newBitmap = new Bitmap(maxX - minx + 1, maxY - minY + 1, bmp.PixelFormat); LockBitmap target = new LockBitmap(newBitmap); target.LockBits(); for (int y = 0; y < target.Height; y++) { for (int x = 0; x < target.Width; x++) { target.SetPixel(x, y, source.GetPixel(x + minX, y + minY)); } } source.UnlockBits(); target.UnlockBits(); newBitmap.Save("d:\\result.png"); } x循环限制为新位图的尺寸,并调整yx值以映射新位置位图到给定像素的原始位置。

请注意,由于初始扫描确定了非白色像素的位置,因此在实际复制像素时无需再次检查。

扫描位图的方法比上述方法更有效。此版本只查看原始位图中的每个像素,跟踪每个坐标的最小值和最大值。我猜这会对你的目的足够快,但是如果你想要更快的东西,你可以改变扫描,以便按顺序扫描每个最小值和最大值:

  1. 扫描y为0的每一行以确定具有非白色像素的第一行。这是最小y值。
  2. 向后扫描y y的每一行,找到最大source.Height - 1值。
  3. 找到最小值和最大y值后,现在扫描y为0的列,找到分钟x,然后从x向后查找最大值{{} {1}}。
  4. 这样做会涉及更多代码,并且可能更难以阅读和理解,但在大多数情况下会涉及检查更少的像素。


    编辑#2:

    以下是第二个代码示例的输出示例: smile, original and cropped side-by-side
    请注意,原始位图的所有白色边框(显示在左侧)都已被裁剪掉,只留下原始位图的最小子集,它可以包含所有非白色像素(如右侧所示)。