Thread: Drawing bitmaps efficiently

  1. #1
    Registered User
    Join Date
    Mar 2007
    Posts
    416

    Drawing bitmaps efficiently

    I'm working on makin some sort of image editor that will resize bitmaps for me. I am currently drawing bitmaps to a "static" window just to make sure i'm loading the bitmaps correctly. So here comes a couple questions.

    1) Is there a way to draw a bitmap to the screen all at once, instead of setting every pixel manually? This obviously takes some time when I'm dealing with images 1280x800 or higher. I found some stuff about HBITMAP but i'm not quite sure if that's what I'm in need of yet as I can't find a definite answer on its use. I think drawing the bitmap this way is also the reason why my CPU usage jumps to 50%+ so it has to be fixed.

    2) The bmp loader I made works well for certain bitmap images. I've used it to load textures for opengl for quite a while and have never had a problem. But now that I'm throwing the image to a static screen, about 1 out of 3 images will end up being slanted with the colors screwed up. This never happened in opengl, and I have tested this on 3 computers and they all do the same thing. What gives?

    Here is the code I'm using to draw the bitmap to the static screen, along with the example of the slanted bitmap.
    Code:
            case WM_PAINT:
            {
                if(UpdateCanvasImage){
                    UINT error;
                    ///set up for displaying picture to the screen
                    InvalidateRect(hCanvas, 0, true); //hCanvas is the static window
                    PAINTSTRUCT ps;
                    HDC winDC = BeginPaint(hCanvas, &ps);
                    
                    for(int y = 0; y < lParam; y++) {
                        for(unsigned int x = 0; x < wParam; x++) {     //g_Canvas is a class defining the bmp image
                            BYTE R = g_Canvas.GetPixel(x, y, PIXEL_R, error);
                            BYTE G = g_Canvas.GetPixel(x, y, PIXEL_G, error);
                            BYTE B = g_Canvas.GetPixel(x, y, PIXEL_B, error);
                            SetPixel(winDC, x, lParam - y, RGB(R, G, B));
                        }
                    }
                    EndPaint(hCanvas, &ps);
                    ///End set up for displaying picture to screen
                    
                    PostMessage(hwnd, RECEIVE_MSG, MSG_SETREADY, 0);
                    UpdateCanvasImage = false;
                    //SetWindowPos(hCanvas, HWND_TOP, 0, 0, wParam, lParam, SWP_SHOWWINDOW);
                }
            }
            break;

  2. #2
    Registered User valaris's Avatar
    Join Date
    Jun 2008
    Location
    RING 0
    Posts
    507
    Load it into a MemDC, do whatever proccessing you need to do, then blast it to the screen with bitblt. Also any of the other methods will draw it pixel by pixel just like you are doing manually, except with a back buffer approach you can avoid the tearing. Also instead of blitting manually like that, MFC probably wraps functionallity around the static window in the CStatic class to do it. Check out CStatic::SetBitmap.

    CStatic::SetBitmap

  3. #3
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Inside of Windows the fastest way to do it is with GDI or GDI+. Any of your methods are going to be considerably slower. Know that GDI and GDI+ are not the fastest graphics APIs out there and they were not built for speed.

  4. #4
    int x = *((int *) NULL); Cactus_Hugger's Avatar
    Join Date
    Jul 2003
    Location
    Banks of the River Styx
    Posts
    902
    2) The bmp loader I made works well for certain bitmap images. I've used it to load textures for opengl for quite a while and have never had a problem. But now that I'm throwing the image to a static screen, about 1 out of 3 images will end up being slanted with the colors screwed up. This never happened in opengl, and I have tested this on 3 computers and they all do the same thing. What gives?
    Show us your code for your bitmap loader. Do you account for things such as color depths being 16/8 bit and RLE encoding? (Google for a spec on the BMP format "BMP file format" on Google, or just visit Wotsit.)
    long time; /* know C? */
    Unprecedented performance: Nothing ever ran this slow before.
    Any sufficiently advanced bug is indistinguishable from a feature.
    Real Programmers confuse Halloween and Christmas, because dec 25 == oct 31.
    The best way to accelerate an IBM is at 9.8 m/s/s.
    recursion (re - cur' - zhun) n. 1. (see recursion)

  5. #5
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    For fast drawing….

    Your WM_PAINT should be a single BitBlt() inside the BeginPaint() / EndPaint() and nothing else.
    All other drawing should be done prior to calling for a WM_PAINT.
    You must allow for WM_PAINT msgs to be generated external to your app (ie by the OS in response to other activities on the PC) and not redraw everything. Your WM_PAINT should only draw the invalidated rectangle and not the whole screen each time.
    Keep a copy of the last screen (in a mem DC) while you construct the next screen.


    I use two mem DCs.
    One holds the current screen image [ScreenDC]
    One holds the image I am constructing from live data etc [FrameDC].
    Both are created the same size as the screen and compatible with it (when the app starts).
    Both are cleaned up on close.

    When you need a new screen;
    Draw the new screen to the FrameDC.
    BitBlt the whole FrameDC to the ScreenDC
    Call for a WM_PAINT msg covering the whole client area.

    When you get a WM_PAINT msg;
    BitBlt the ScreenDC to the HDC in the PAINTSTRUCT using the RECT in the PAINTSTRUCT.

    NOTE: For fast drawing use BOTH
    InvalidateRect() [generates a WM_PAINT msg]
    and
    UpdateWindow() [sends the WM_PAINT directly to the callback, not via the OS msg queue].
    NEVER call InvalidateRect() (or UpdateWindow() ) inside the WM_PAINT msg, it causes an infinte loop (negated in your code by your drawing flag).

    Do a search on 'double buffering' here for more code.
    Last edited by novacain; 06-07-2009 at 10:46 PM.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  6. #6
    Unregistered User Yarin's Avatar
    Join Date
    Jul 2007
    Posts
    2,158
    Rows of bitmap data are 4-byte aligned. It's slanting because your DIB decoder is reading the padding, ignoring it's presence. It's actually an easy fix.

  7. #7
    Registered User
    Join Date
    Mar 2007
    Posts
    416
    Sorry I did not get back to this right away, been pretty busy.

    After getting the header and info from the bmp file this is how I do the checking/byte reading. Right now I do not take in to account anything but 24 bit bmp files.
    @ Yarin, how is it an easy fix? I've always read bmp files in with this function for OpenGL and it rendered the textures correctly every time.

    @novacain, I always like your ideas, I'll work on trying that.

    One other thing I can't quite get. The way my WM_PAINT message is in the first post, the program takes up 50% cpu power, basically one of two cores. If I comment everything out of the message, but leave the message there so it is essentially empty, it still uses 50%. If I comment the message out, then it idles at 0-2% like it should. So... could repeated meaningless Post/SendMessage calls do this, or is there something else that can cause it?

    EDIT: I should mentioned that I replace WM_PAINT with WM_DRAWSCREEN (my definition), and the cpu % went back down to 0. All that I changed was the message name, so is the WM_PAINT message being over used somehow?

    Code:
        if(fileheader.bfType != 0x4D42){
            printf("***BMP load error: bad file format***\n");
            return false;
        }
    
        if(infoheader.biBitCount != 24){
            printf("***BMP load error: invalid color depth***\n");
            return false;
        }
    
        if(infoheader.biCompression != BI_RGB){
            printf("BMP image is compressed\n");
            return false;
        }
        
        width = infoheader.biWidth; //assign width value
        height = infoheader.biHeight; //assign height value
        
        (*pixelmap) = (unsigned char*)malloc(width*height*3);
        //ZeroMemory(*pixelmap, width*height*3);
        unsigned char alpha;
    
        for (int i = 0; i < infoheader.biWidth*infoheader.biHeight; i++)
        {
            fread(&rgbt, sizeof(RGBTRIPLE), 1, l_file);
    
            (*pixelmap)[j+0] = rgbt.rgbtRed; // Red component
            (*pixelmap)[j+1] = rgbt.rgbtGreen; // Green component
            (*pixelmap)[j+2] = rgbt.rgbtBlue; // Blue component
            j += 3; // Go to the next position
        }
    
        fclose(l_file); //closes the file stream
    
        return true; //returns true if it was successful
    }
    Last edited by scwizzo; 06-12-2009 at 03:55 PM.

  8. #8
    int x = *((int *) NULL); Cactus_Hugger's Avatar
    Join Date
    Jul 2003
    Location
    Banks of the River Styx
    Posts
    902
    See the Wikipedia page on BMP. In that table, there is a row which reads "Padding for 4 byte alignment (Could be a value other than zero)" - look at the corresponding data too. That is what Yarin mentions.

    And I thought OpenGL provided a BMP file loader?
    long time; /* know C? */
    Unprecedented performance: Nothing ever ran this slow before.
    Any sufficiently advanced bug is indistinguishable from a feature.
    Real Programmers confuse Halloween and Christmas, because dec 25 == oct 31.
    The best way to accelerate an IBM is at 9.8 m/s/s.
    recursion (re - cur' - zhun) n. 1. (see recursion)

  9. #9
    Registered User
    Join Date
    Mar 2007
    Posts
    416
    Quote Originally Posted by Cactus_Hugger View Post
    And I thought OpenGL provided a BMP file loader?
    It probably does. I never found anything for one, but I didn't look very hard either (and by not very hard i mean maybe once). Once I made a working bmp loader I did away with looking for a loader.

  10. #10
    Registered User
    Join Date
    Mar 2007
    Posts
    416
    Alright... I got the alignment down, but came up with this messed up problem that I can't trace down. I resaved the file as I loaded it to make sure I am properly loading the file, and when I view the resaved file it looks exactly as the original so I know I am loading it correctly. Here's what's happening, at some width, that doesn't change, the image drops off, then after that same width the image comes back and it repeats in this manner. There's a couple pictures to show what I am talking about. As you can see the static window in grey has the correct width/height so I know it is not a problem with that. Also, the code below is how I am loading the bitmap, and how it is being displayed to the screen.

    Code:
    //After doing the header/info reading...
        width = infoheader.biWidth; //assign width value
        height = infoheader.biHeight; //assign height value
        
        (*pixelmap) = (unsigned char*)malloc(width*height*3);
    
        //ZeroMemory(*pixelmap, width*height*3);
        unsigned char padding;
    
        for (int i = 0; i < infoheader.biWidth*infoheader.biHeight; i++)
        {
            fread(&rgbt, sizeof(RGBTRIPLE), 1, l_file);
            
            (*pixelmap)[j+0] = rgbt.rgbtRed; // Red component
            (*pixelmap)[j+1] = rgbt.rgbtGreen; // Green component
            (*pixelmap)[j+2] = rgbt.rgbtBlue; // Blue component
            j += 3; // Go to the next position
            
            if(i % infoheader.biWidth == 0){
                for(int n = 0; n < infoheader.biWidth % 4; n++)
                    fread(&padding, 1, 1, l_file);
            }
        }
        
        fclose(l_file); //closes the file stream
    
    
    
    //Drawing proceedure, now it's not in WM_PAINT
    //All the 'error' calls return ERR_SUCCESS, so it's not hitting out of bounds
    
            case WM_DRAWCANVAS:
            {
                if(UpdateCanvasImage){
                    printf("UpdateCanvasImage = true\n");
                    UINT error;
                    ///set up for displaying picture to the screen
                    InvalidateRect(hCanvas, 0, true);
                    
                    PAINTSTRUCT ps;
                    HDC winDC = BeginPaint(hCanvas, &ps);
                    
                    for(int y = 0; y < g_Canvas.Height(); y++){
                        for(int x = 0; x < g_Canvas.Width(); x++){
                            
                            BYTE R = g_Canvas.GetPixel(x, y, PIXEL_R, error);
                            if(error != ERR_SUCCESS) cout<< error <<endl;
                            
                            BYTE G = g_Canvas.GetPixel(x, y, PIXEL_G, error);
                            if(error != ERR_SUCCESS) cout<< error <<endl;
                            
                            BYTE B = g_Canvas.GetPixel(x, y, PIXEL_B, error);
                            if(error != ERR_SUCCESS) cout<< error <<endl;
                            
                            SetPixel(winDC, x, lParam - y, RGB(R, G, B));
                        }
                    }
                    EndPaint(hCanvas, &ps);
                    ///End set up for displaying picture to screen
                    
                    PostMessage(hwnd, RECEIVE_MSG, MSG_SETREADY, 0);
                    UpdateCanvasImage = false;
                }
            }
            break;
    EDIT: sorry for the size, I don't know how to make the images stacked.
    Last edited by scwizzo; 06-13-2009 at 09:24 AM.

  11. #11
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Not exactly sure, but why are you using 'lParam - y'? Also, you're still using InvalidateRect incorrectly. Remember, the purpose of the function is to mark a certain region as 'invalid' and, if the last parameter is TRUE, it generates a WM_PAINT message (so it obviously doesn't make sense to call it during the *painting* process). Logically, it should be called whenever you want to update the screen (such as after loading the image). Finally, BeginPaint/EndPaint should be handled exclusively in response to the WM_PAINT message.
    Last edited by Sebastiani; 06-13-2009 at 02:26 PM.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  12. #12
    Registered User
    Join Date
    Mar 2007
    Posts
    416
    The bitmap is stored in the file upside down, so my array that has the pixel information is upside down and needs to be swapped when drawing it to the screen, so basically 'lParam - y' is 'Height - current height' since I'm passing the width and height through the wParam and lParam arguments to cut back on global variables. (yes I know my class is global and can call the width and height).

    I tried using the WM_PAINT message, and for some reason (with or without InvalidateRect() ) just the fact that I'm using that message jumps the cpu % to 50. If I use the message I have in my last post, the cpu stays down.

    I've found a few things out, but none of it is making sense
    -the loader is loading the files properly
    -the drawing may have something to do with it, not sure yet
    -the open file does not have to do with this (since all it does is call LoadBMP and post a message)
    -the width, height, and image are being set correctly
    -the static window initializes to the correct size

    This is not making any sense to me.

  13. #13
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    >> so basically 'lParam - y' is 'Height - current height'

    OK, got it.

    I would suspect then that the problem might be in the implementation of GetPixel (whatever the type of g_Canvas is). An easy way to test this would be to do something like:

    Code:
    SetPixel(winDC, x, lParam - y, RGB(0, 0, 255));
    If you get a solid, unbroken rectangle, then the implementation is somehow broken. Otherwise, I would recommend posting more code so that we can get a better idea of what else could be the problem.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  14. #14
    Registered User
    Join Date
    Mar 2007
    Posts
    416
    Quote Originally Posted by Sebastiani View Post
    If you get a solid, unbroken rectangle, then the implementation is somehow broken. Otherwise, I would recommend posting more code so that we can get a better idea of what else could be the problem.
    That was a good idea, but I still get the broken image in the exact same format as shown above. The code for my GetPixel function is there. I'm almost certain it's not that since I use to have a global array of the same thing and got the pixel in the same fashion and it worked correctly. This use to work, even with my CANVAS class, then I changed a few things and didn't remember what I changed, and it didn't work

    I'm working on using novacain's idea on the BitBlt, hoping my problem goes away somewhere in the process. I'm still completely confused on why my exe file creates 50% cpu with just the WM_PAINT message being present. I tested this by creating a new project, changed nothing except adding case WM_PAINT: {} break; and boom, cpu shoots up. I have no idea why. So until that also gets figured out my paint message will be replaced by my own defined message that acts as WM_PAINT.

    Code:
    unsigned char CANVAS::GetPixel(int col /*x*/, int row /*y*/, UINT color, UINT &error)
    {
        if(m_empty){
            error = ERR_NO_CANVAS;
            return 0;
        }
        else if(col > m_width){
            error = ERR_WIDTH_TOO_LARGE;
            return 0;
        }
        else if(row > m_height){
            error = ERR_HEIGHT_TOO_LARGE;
            return 0;
        }
        
        //PIXEL_R = 9
        //PIXEL_G = 8
        //PIXEL_B = 7
        error = ERR_SUCCESS;
        return m_im_rgb[(col + row*m_width)*3 + PIXEL_R - color];
    }

  15. #15
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Except for an out-of-bounds bug (if col == m_width or row == m_height you're going to overstep the array), the function appears OK. It isn't very efficient though since you have to call it three times to get the color values. I would recommend returning the entire pixel, personally. But I still don't see how you're getting that output (although if you call InvalidateRect/BeginPaint/EndPaint in the wrong manner you could certainly run into this problem).

    >> I'm still completely confused on why my exe file creates 50% cpu with just the WM_PAINT message being present.

    Without seeing more code (which would also help with pinpointing the first problem), it's impossible to say.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Slow drawing code
    By tjpanda in forum Windows Programming
    Replies: 5
    Last Post: 05-09-2008, 05:09 PM
  2. drawing on bitmaps
    By eth0 in forum Windows Programming
    Replies: 2
    Last Post: 03-24-2006, 05:56 PM
  3. Interesting problem with bmp drawing
    By Victor in forum C++ Programming
    Replies: 3
    Last Post: 04-12-2005, 10:39 AM
  4. MFC: Multiple clicks shows multiple bitmaps - how?
    By BrianK in forum Windows Programming
    Replies: 0
    Last Post: 06-20-2004, 07:25 PM
  5. Drawing Bitmaps
    By filler_bunny in forum Windows Programming
    Replies: 6
    Last Post: 05-05-2004, 04:32 PM