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
精彩评论