Search code examples
visual-c++mfcgdi

Access individual pixels in CBitmap object


As an exercise, I am attempting to write a piece of code which can sample a single pixel from an MFC CBitmap object, at a particular x/y location.

The class does not have any GetPixel type interface, and most of the information I have seen indicates copying the entire contents of the CBitmap bits via CBitmap::GetBitMapBits, which seems extremely inefficient.

Is there no way to gain access to the byte array via pointer and access it as an array?


Solution

  • If the CBitmap object is associated with a device-independent bitmap (DIB, created by CreateDIBSection() for instance), you can get a pointer to directly access the bitmap pixels (without copying) by calling GetObject(). Make sure to call GdiFlush() if you have accessed the bitmap pixels by any other GDI functions before using direct access.

    If the CBitmap is associated with a device-dependent bitmap (DDB, also known as compatible bitmap), which method to use depends on how many pixels you want to access.

    • If only a handful of pixels need to be accessed, you may go the CDC::SelectObject(), CDC::GetPixel() route. This will be very slow if you want to read a bigger number of pixel.
    • To access a big number of pixels, you may use either CBitmap::GetBitMapBits() or GetDIBits(). The latter may be more efficient when you only need to access part of the bitmap pixels because it has parameters to define a range of scanlines to copy.

    In either case, a DDB will always be slower than a DIB when you need to access it pixel-by-pixel.

    The following example detects if a CBitmap is associated with either a DIB or a DDB and branches to use the most efficient access method for each case.

    void DoAwesomeStuff( CBitmap& bitmap )
    {
        DIBSECTION dib{ 0 };
        if( ::GetObject( bitmap, sizeof( dib ), &dib ) )
        {
            // GetObject() succeeded so we know that bmp is associated with a DIB.
    
            // Evaluate the information in dib thoroughly, to determine if you can handle
            // the bitmap format. You will propably restrict yourself to a few uncompressed 
            // formats.
            // In the following example I accept only uncompressed top-down bitmaps 
            // with 32bpp.
            if( dib.dsBmih.biCompression == BI_RGB && 
                dib.dsBmih.biHeight < 0 &&  // negative height indicates top-down bitmap
                dib.dsBmih.biPlanes == 1 && 
                dib.dsBmih.biBitCount == 32 )
            {
                DWORD* pPixels = reinterpret_cast<DWORD*>( dib.dsBm.bmBits );
                // TODO: Access the bitmap directly through the pPixels pointer. 
                // Make sure to check bounds to avoid segfault.
            }
        }
        else
        {
            // GetObject() failed because bmp is not a DIB or for some other reason.
            BITMAP bmp{ 0 };
            if( ::GetObject( bitmap, sizeof( bmp ), &bmp ) )
            {
                // GetObject() succeeded so we know that bmp is associated with a DDB.
                CDC dc;
                // Create a memory DC.
                dc.CreateCompatibleDC( nullptr );
                if( CBitmap* pOldBmp = dc.SelectObject( &bitmap ) )
                {
                    // Get the bitmap pixel at given coordinates.
                    // For accessing a large number of pixels, CBitmap::GetBitMapBits() 
                    // or GetDIBits() will be more efficient.
                    COLORREF pixel = dc.GetPixel( 42, 24 );
    
                    // Standard cleanup: restore the bitmap that was originally 
                    // selected into the DC.
                    dc.SelectObject( pOldBmp );
                }
                else
                {
                    // TODO: handle error               
                }
            }
            else
            {
                // TODO: handle error
            }
        }
    }