tags:

views:

134

answers:

5

Hi,

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 way?

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
+2  A: 

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

Lucero
A: 

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).

schnaader
+1  A: 

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.

Hans Passant
A: 

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).

Darknight
A: 

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
Kris Erickson