开发者

efficient way to detect if an image is empty

I need a very fast method to detect if an image is empty. Im my case then all pixels are white and transparant. The images are png's. My current method is to load them in a memory bitmap and check each pixel value, but this is way to slow. Is there a more efficient w开发者_如何学Pythonay?

This is my current code:

'Lock the bitmap bits.  
    Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(rectBmp, _
        Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat)

    Try
        Dim x As Integer
        Dim y As Integer

        For y = 0 To bmpData.Height - 1
            For x = 0 To bmpData.Width - 1
                If System.Runtime.InteropServices.Marshal.ReadByte(bmpData.Scan0, (bmpData.Stride * y) + (4 * x) + 3) <> 0 Then
                    Return True
                    Exit For
                End If
            Next
        Next
    Finally
        bmp.UnlockBits(bmpData)
    End Try


You can "map" a GDI+ bitmap onto a pinned byte[] (or int[] or even uint[]) and by this directly use the array to read and write the ARGB bitmap data.

The following CodeProject article explains how to do it: http://www.codeproject.com/KB/GDI-plus/pointerlessimageproc.aspx


Sure, divide the image by a set amount, for each set start a new thread to only check for non blank pixels (eg not white/transparent).

Create an event that fires (and sets a flag) only if a non empty pixel is found. Have each thread to check this flag (basically a while loop).


Using Marshal.ReadInt64() will give you an immediate x 8 speed boost. Watch out for overshooting the Width though, you'll need ReadByte() for the last few pixels in a scan line. Writing this code in C# in a helper assembly is probably the quickest fix, it allows you to use a pointer.


Using C# (I know you sample is in VB.Net), you can use unsafe and get very fast access to your bitmap:

        const int ALPHA_PIXEL = 3;
        const int RED_PIXEL = 2;
        const int GREEN_PIXEL = 1;
        const int BLUE_PIXEL = 0;

        try
        {
            BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);

            int bytesPerPixel = (bmp.PixelFormat == PixelFormat.Format24bppRgb ? 3 : 4);
            int stride = bmData.Stride;

            unsafe
            {
                byte* pixel = (byte*)(void*)bmData.Scan0;
                for (int y = 0; y < bmp.Height; y++)
                {
                    int yPos = y * stride;
                    for (int x = 0; x < bmp.Width; x++)
                    {
                        int pos = yPos + (x * bytesPerPixel);

                        if (pixel[pos + RED_PIXEL] != 255 || pixel[pos + GREEN_PIXEL] != 255 || pixel[pos + BLUE_PIXEL] != 255 ||
                            pixel[pos + ALPHA_PIXEL] != 0)
                            {
                                return true;
                            }
                    }

                }
            }
        }
        finally 
        {
            bmp.UnlockBits(bmData);
        }
        return false;

If you can't use C#, then the fastest way would be to use Marshal.Copy as a block:

    Dim bmData As BitmapData = bmp.LockBits(New Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat)

    Try

        Dim bytesPerPixel As Integer = If(bmp.PixelFormat = PixelFormat.Format24bppRgb, 3, 4)
        Dim stride As Integer = bmData.Stride
        Dim imageSize As Integer = stride * bmp.Height

        Dim pixel As Byte()
        ReDim pixel(imageSize)
        Marshal.Copy(bmData.Scan0, pixel, 0, imageSize)

        For y As Integer = 0 To bmp.Height - 1
            Dim yPos As Integer = y * stride
            For x As Integer = 0 To bmp.Width - 1
                Dim pos As Integer = yPos + (x * bytesPerPixel)

                If pixel(pos + RED_PIXEL) <> 255 OrElse pixel(pos + GREEN_PIXEL) <> 255 OrElse pixel(pos + BLUE_PIXEL) <> 255 OrElse pixel(pos + ALPHA_PIXEL) <> 0 Then
                    Return
                End If

            Next
        Next
    Finally
        bmp.UnlockBits(bmData)
    End Try
    Return


The most efficient way would be to manually parse the file, directly jump to the datastream and read the compressed zLib stream until you find a pixel that is not white and transparent.

Depending on where the pictures come from and how much they make use of various PNG "specials" (filters, special palette...), this can be much effort, so read "efficient" as "a lot of work, but very fast".

Useful resources for this are the PNG datastream specification and the ZLIB specification (or, much easier, a zLib library).


There is a c# wrapper for Image Magick:

http://sourceforge.net/projects/imagemagickapp/

Use the identify feature of ImageMagik CLI as given here:

http://www.imagemagick.org/script/identify.php

With command:

identify -format "%#" source.png

If the number of colors is 1, you have a blank page.

You can also use the command:

identify -verbose source.png

The standard deviation, skew and kurtosis will be 0 for a blank image.


I would use GDI+ to convert the PNG file to BMP and save it to a temporary location. Then I would leverage its uncompressed nature, seek beyond the header into the pixels, prepare a medium-sized (say 16384 B) temporary string full of zero bytes and then read 16384B chunks until the end of file, comparing them to the temporary string. Since Windows XP and higher gives zero bytes when reading past end of file, no special treatment of the end is needed, just read a bit over the end with your last iteration. Make sure the bitmap is saved in 32-bit mode or black opaque pixels won't be detected. This mode will also take care of any palettes and other quirks so that you can even seek to a fixed post-header offset.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜