开发者

What's an efficient way to tell if a bitmap is entirely black?

I'm wondering if there's a super-efficient way of confirming that an Image object references an entirely black image, so every pixel within the bitmap is ARGB(255, 0, 0, 0).

What would you recommend? Most of these bitmaps will be 1024 x 6000 pixels (although it's not safe to assume they'll always be that size).

I need this because we're having problems with the PrintWindow API. We find that nearly 20% of the time, at least some part of the image will be a black square (a subsequent capture will succeed). My idea to work around this was to call PrintWindow or WM_PRINT with each child window, then piece the whole image of the window back together. If I can find an efficient way of detecting that PrintWindow returned a black image for a particular child window, then I can quickly call PrintWindow again on that capture. It sucks, but PrintWindow is the only method of capturing a window that works on all windows (that I want, anywa开发者_JAVA技巧y) and supports capturing windows that are hidden and/or off-screen.

When PrintWindow fails, it doesn't set an error code or return anything that indicates it failed. When it has this black square problem, it's always an entire window or child window that returns black. So by capturing each child window separately, I can be sure that each of my captures will have worked, providing it contains at least one non-black pixel.

PrintWindow is better in Vista and above, apparently, but in this case we're limited to Server 2003.


I'd recommend you to lock the bitmap in the memory using the LockBits method of the System.Drawing.Bitmap type. This method returns the BitmapData type, from which you can receive a pointer to the locked memory region. Then iterate through the memory, searching for the non-zero bytes (really, faster by scanning for the Int32 or even Int64 values, depending on the platform you use). Code will look like this:

// Lock the bitmap's bits.  
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData =bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);

// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;

// Declare an array to hold the bytes of the bitmap.
int bytes  = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];

// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, bytes);

// Scanning for non-zero bytes
bool allBlack = true;
for (int index = 0; index < rgbValues.Length; index++)
    if (rgbValues[index] != 0) 
    {
       allBlack = false;
       break;
    }
// Unlock the bits.
bmp.UnlockBits(bmpData);

Consider using the unsafe code and direct memory access (using pointers) to improve performance.


The first answer to this post is Awesome. I modified the code to more generically determine if the image is all one color(all black, all white, all magenta, etc...). Assuming you have a bitmap with 4 part color values ARGB, compare each color to the color in the top left if any is different then the image isn't all one color.

private bool AllOneColor(Bitmap bmp)
{
    // Lock the bitmap's bits.  
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);

    // Get the address of the first line.
    IntPtr ptr = bmpData.Scan0;

    // Declare an array to hold the bytes of the bitmap.
    int bytes = bmpData.Stride * bmp.Height;
    byte[] rgbValues = new byte[bytes];

    // Copy the RGB values into the array.

    System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

    bool AllOneColor = true;
    for (int index = 0; index < rgbValues.Length; index++)
    {
        //compare the current A or R or G or B with the A or R or G or B at position 0,0.
        if (rgbValues[index] != rgbValues[index % 4])
        {
            AllOneColor= false;
            break;
        }
    }
    // Unlock the bits.
    bmp.UnlockBits(bmpData);
    return AllOneColor;
}


If you knew more about the conditions under which the image would be non-black, it would be easier. For example, what do the edges or center of the image look like when it's non-black. Essentially, what you create is heuristic to guess at a non-black image and sample those areas that will give you the quickest read on it. If your heuristic indicates a all-black image, then you can either decide that it is all-black or do a full check of all pixels. That's heavily dependent on your images, though. If you have to be able to distinguish between an all-black image and one containing a single non-black pixel in a random location, you'll have to check them all.


Lock the bitmap into memory and scan it with bitwise operations. Don't use GetPixel and the like; that's slow.


Using AForgeNET library (http://www.aforgenet.com) could also be a solution:

public bool IsNotBlackImage()
{
    Assembly assembly = this.GetType().Assembly;
    var imgTest = new Bitmap(assembly.GetManifestResourceStream("TestImage.png"));
    var imgStatistics = new ImageStatistics(imgTest);             
    return imgStatistics.PixelsCountWithoutBlack != 0;
}

For ImageStatistics class reference AForge.Imaging.dll in your project.

http://code.google.com/p/aforge/source/browse/trunk/Sources/Imaging/ImageStatistics.cs


Draw the bitmap with a ColorMatrix that has 3 x 255 in the diagonal, that will blow any non-black pixel to pure white. Then draw that bitmap to a smaller one whose width is a multiple of 4 and has the Format24bppRgb format. That eliminates the alpha, reduces the size and leaves only zeros if the bitmap is truly black.

You'll have to experiment to see how small you can make the bitmap, use a sample one that has only one white pixel to see when the interpolator makes it disappear. I'm guessing you can go pretty far.


To be entirely sure of the image's blackness, you will have to check every pixel, and accessing the pixel data in an unsafe block is likely the fastest way to do it. Of course it's possible to optimize for the non-black case and try to find those earlier, but in the worst case you'll always have to check each and every pixel.


Just some random thoughts:

  • Maybe you could apply a ColorMatrix to the original bitmap (to completely turn it to black). Then compare the result with the original.
  • Or create a bitmap of identical size (filled with pure black) and then compare to the original bitmap.


A reasonably reliable method would be to check the file size of the image. That is, if the images that are not all black have a relatively normal distribution of colors.

If you know the file type, you know some basic things about the average compression ratios. And you can determine the dimensions of the file pretty readily without cycling through the whole file.

An all black image, of any dimension, using a compressed file format, is going to have a very small file size compared to an image of identical dimensions with a fairly normal distribution of colors.

This method would take a little bit of time to test and build up a knowledge base of just what the file size of an all-black image should be compared to a non-all-black image, but it would be very fast.

If you have many instances where the non-all-black images are pretty close to all-black, obviously then this method would not work.


I have an idea that's outside the box.

What about a CRC checksum? You could first check the dimensions of the image, then calculate the checksum and compare that to known (precalculated) checksums of an all-black image of the same dimensions.

EDIT: I doubt this would be any faster than @leonard's method. The only reason it might is if the original file was not a bitmap, but was a compressed image format. That way, the CRC checksum algorithm wouldn't have to uncompress the image before running.


    private bool AllOneColor(Bitmap bmp)
    {
        BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
        byte[] rgbValues = new byte[bmpData.Stride * bmpData.Height];
        System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, rgbValues, 0, rgbValues.Length);
        bmp.UnlockBits(bmpData);
        return !rgbValues.Where((v, i) => i % bmpData.Stride < bmp.Width && v != rgbValues[0]).Any();
    }


One trick that may do it as well is put an indicator pixel somewhere, which always has the same color, unless the image capture fails, in which case everything would be completely black I assume

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜