开发者

Windows splash screen using GDI+

The eventual aim of this is to have a splash screen in windows that uses transparency but that's not what I'm stuck on right now.

In order to create a transparent window, I'm first trying to composite the splash screen and text on an off screen buffer using GDI+.

Currently, I'm just trying to composite the buffer and display it in response to a 'WM_PAINT' message. This isn't working out at the moment; all I see is a black window.

I imagine I've misunderstood something with regards to setting up render targets in GDI+ and then rendering them (I'm trying to render the screen using straight forward GDI blit)

Anyway, here's the code so far:

//my window initialisation code
void MyWindow::create_hwnd(HINSTANCE instance, const SIZE &dim)
{                       
    DWORD ex_style = WS_EX_LAYERED ; //eventually I'll be making use of this layerd flag 
    m_hwnd = CreateWindowEx(
            ex_style,
            szFloatingWindowClass , 
            L"", 
            WS_POPUP  ,
            0, 
            0,       
            dim.cx, 
            dim.cy,
            null, 
            null, 
            instance, 
            null);      


      m_display_dc = GetDC(NULL);
      //This was sanity check test code - just loading a standard HBITMAP and displaying it in WM_PAINT. It worked fine
      //HANDLE handle= LoadImage(NULL , L"c:\\test_image2.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);       
      m_gdip_offscreen_bm = new Gdiplus::Bitmap(dim.cx, dim.cy);

      m_gdi_dc = Gdiplus::Graphics::FromImage(m_gdip_offscreen_bm);//new Gdiplus::Graphics(m_splash_dc );//window_dc ;m_splash_dc       

        //this draws the conents of my splash screen - this works if I create a GDI+ context for the window, rather than for an offscreen bitmap. 
        //For all I know, it might actually be working but when I try to display the contents on screen, it shows a black image
      draw_all();
        //this is just to show that drawing something simple on the offscreen bit map seems to have no effect
      Gdiplus::Pen pen(Gdiplus::Color(255, 0, 0, 255));
      m_gdi_dc->DrawLine(&pen, 0,0,100,100);

      DWORD last_error = GetLastError(); //returns '0' at this stage                
}

And here's the snipit that handles the WM_PAINT message:

---8<-----------------------
//Paint message snippit

    case WM_PAINT:
    {
        BITMAP bm;
        PAINTSTRUCT ps;

        HDC hdc = BeginPaint(vg->m_hwnd, &ps); //get the HWNDs DC

        HDC hdcMem = vg->m_gdi_dc->GetHDC();   //get the HDC from our offscreen GDI+ object

        unsigned int width = vg->m_gdip_offscreen_bm->GetWidth();  //width and height seem fine at this point
        unsigned开发者_开发知识库 int height = vg->m_gdip_offscreen_bm->GetHeight();
        BitBlt(hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);   //this blits a black rectangle

        DWORD last_error = GetLastError();              //this was '0'

        vg->m_gdi_dc->ReleaseHDC(hdcMem);  

        EndPaint(vg->m_hwnd, &ps);  //end paint

        return 1;
    }

    ---8<-----------------------

My apologies for the long post. Does anybody know what I'm not quite understanding regarding how you write to an offscreen buffer using GDI+ (or GDI for that matter)and then display this on screen?

Thank you for reading.


Solved it, although I'm sure this isn't optimal. Firstly, as Chris Becke suggested, you shouldn't respond to the WM_PAINT message and should only update using the 'UpdateLayeredWindow'. One of the problems I had was that gave UpdateLayeredWindow some bad coordinates, specifically I'd swapped the source and destination around (doh!)

Here's the code for calling UpdateLayeredWindow from a GDI+ surface. It's not optimal but it works.

void TOPLEVEL_FLOATING_WINDOW::update_layered_window()
{
    BLENDFUNCTION blend_func = { 0 };     //build the blend function object... I guess microsoft doesn't/didn't like constructors
    blend_func.AlphaFormat = AC_SRC_OVER;
    blend_func.BlendFlags = 0;
    blend_func.SourceConstantAlpha = 255;
    blend_func.AlphaFormat = AC_SRC_ALPHA;

    POINT_2i  pt = m_screen_pos;
    VECTOR_2i dim = m_bounds.dimensions();

    POINT_2i  pt_src (0,0) ;            

    Gdiplus::Color bg(0,0,0,0);
    m_gdip_offscreen_bm->GetHBITMAP(bg, &m_offscreen_bm);   //get the Hbitmap from my GDI+ bitmap - I think this is allocating a whole new bitmap off the heap. Yuck, very inefficient!
    HDC  splash_dc = CreateCompatibleDC(m_display_dc);      //create a temporary HDC        
    HGDIOBJ oldobj = SelectObject(splash_dc , m_offscreen_bm);  //'select' the bitmap.      
    UpdateLayeredWindow(m_hwnd,m_display_dc, (POINT*)&pt, (SIZE*)&dim, splash_dc , (POINT*)&pt_src, 0 ,&blend_func,ULW_ALPHA); //this call works and updates our splash screens hidden buffer       
    SelectObject(splash_dc, oldobj);    //some tidy up code
    DeleteObject(m_offscreen_bm);       //free the bitmap. Memory fragmentation HO!
    DeleteDC(splash_dc); //Delete the DC        
}

And here's what I think should work but does not. I'll hand out some points to anybody who can tell me why not! I imagine this is because HDCs are not born equal and the UpdateLayeredWindow function can only accept HDCs created by a certain source in a certain way. It would be nice if the rules for this were more obvious.

void TOPLEVEL_FLOATING_WINDOW::update_layered_window_in_an_ideal_world()
    {
        m_refresh_needed = false;
        BLENDFUNCTION blend_func = { 0 };
        blend_func.AlphaFormat = AC_SRC_OVER;
        blend_func.BlendFlags = 0;
        blend_func.SourceConstantAlpha = 255;
        blend_func.AlphaFormat = AC_SRC_ALPHA;

        POINT_2i  pt = m_screen_pos;
        VECTOR_2i dim = m_bounds.dimensions();

        POINT_2i  pt_src (0,0) ;            

        HDC gdi_HDC = m_screen_gdi_dc->GetHDC();    //I have a GDI+ 'Graphics' object whose back buffer is the image I want to composite on to the desktop      
        UpdateLayeredWindow(m_hwnd,m_display_dc, (POINT*)&pt, (SIZE*)&dim, gdi_HDC , (POINT*)&pt_src, 0 ,&blend_func,ULW_ALPHA);
        m_screen_gdi_dc->ReleaseHDC(gdi_HDC);   //be nice and release the gdi_HDC
    }

The only other alternative is to ignore GDI+ and render everything using my own raster library, which is quite happy to render to any offscreen buffer. However, that's kind of overkill for this project.


Here is a method of getting GDI+ bitmaps into a layered window. This allows multiple bitmap overlays, positioning and re-sizing.

void SetSplashImage()
{
  // Default to upper left of screen
  POINT ptOrigin = { 0, 0 };
  SIZE sizeSplash = { 128, 128 };

  // Get the actual screen location
  GetPointOfOrigin(ptOrigin, sizeSplash);

  // Our in memory database of GDI+ Bitmaps
  data::image::BoxOfBits *box = 
    dynamic_cast<data::image::BoxOfBits *>(&Images.get_package("skin_layout_008"));

  // Create a display context as a canvas to draw the images
  HDC hdcScreen = GetDC(NULL);
  HDC hdcMem = CreateCompatibleDC(hdcScreen);
  HBITMAP bmMem = CreateCompatibleBitmap(hdcScreen, sizeSplash.cx, sizeSplash.cy);

  // Prep canvas for rendering graphic
  HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcMem, bmMem);
  Gdiplus::Graphics graphics(hdcMem);

  // Base image is a pic of a folder
  Gdiplus::RectF canvasDest(0,0,128,128);
  graphics.DrawImage(box->grab("black_folder").pBitmap_, 
    canvasDest,  0,0,128,128,  Gdiplus::UnitPixel);

  // Overlay a pic of some tools in center of folder 
  Gdiplus::RectF canvasDest2(30,50,64,64); // resize half actual size
  graphics.DrawImage(box->grab("work_tools").pBitmap_,
    canvasDest2,  0,0,128,128,  Gdiplus::UnitPixel);

  // Overlay a pic of a cog in upper left corner of folder
  Gdiplus::RectF canvasDest1(16,16,32,32); // resize half actual size
  graphics.DrawImage(box->grab("cog_edit").pBitmap_,
    canvasDest1,  0,0,32,32,  Gdiplus::UnitPixel);

  // Prepare to alpha blend the canvas with the screen
  BLENDFUNCTION blend = { 0 };
  blend.BlendOp = AC_SRC_OVER;
  blend.SourceConstantAlpha = 255;
  blend.AlphaFormat = AC_SRC_ALPHA;

  // Composite the canvas with the screen into the layered window
  POINT ptZero = { 0 };
  UpdateLayeredWindow(hwnd_, hdcScreen, &ptOrigin, &sizeSplash,
      hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);

  // Delete temporary objects used for canvas
  SelectObject(hdcMem, hbmpOld);
  DeleteObject(bmMem);
  DeleteDC(hdcMem);
  ReleaseDC(NULL, hdcScreen);

  // Update mouse hit-test coordinates
  GetWindowRect(hwnd_, &rcMousedown_);
}

The layered window composed of the three bitmaps look like this on the screen : -- guess I don't have enough reputation to post the image :(

Here's the link though - http://i.stack.imgur.com/HmU7H.png

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜