WM_TIMER animation flickering - c++

Alright I'm using a timer with a 50 ms elapse time to animate some moving text (technically its scrolling between text).
Problem is, if you look closely, you can see the text flickering, and id like it not to flicker..
So i'm not that good with animation but is there something I can do to reduce flickering? Maybe a faster elapse time? Or should I even be using a timer for this at all?
EDIT:
So i've tried to implement double buffering, and i'm obviously doing something.
Here is the code without double buffering, this works fine but flickers a little.
void PaintScrollingText(ScrollingText *Settings, WPARAM wParam)
{
HDC hdc;
PAINTSTRUCT ps;
HANDLE hOldFont;
RECT rect;
hdc = wParam ? (HDC)wParam : BeginPaint(Settings->hWnd, &ps);
hOldFont = SelectObject(hdc, Settings->hFont);
SetTextColor(hdc, Settings->crForeGnd);
SetBkColor(hdc, Settings->crBackGnd);
GetClientRect(Settings->hWnd, &rect);
rect.right -= Settings->txt1XOffset;
DrawText(hdc, Settings->szText1, -1, &rect, DT_RIGHT);
rect.right += Settings->txt1XOffset - Settings->txt2XOffset;
DrawText(hdc, Settings->szText2, -1, &rect, DT_RIGHT);
SelectObject(hdc, hOldFont);
if (!wParam) EndPaint(Settings->hWnd, &ps);
}
And this is my code, with double buffering.
void PaintScrollingText(ScrollingText *Settings, WPARAM wParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
GetClientRect(Settings->hWnd, &rect);
hdc = wParam ? (HDC)wParam : BeginPaint(Settings->hWnd, &ps);
// Create off-screen DC
HDC hdcMem = CreateCompatibleDC(hdc);
// Create a bitmap to draw on
HBITMAP MemBitmap = CreateCompatibleBitmap(hdc, rect.right - rect.left, rect.bottom - rect.top);
// Select bitmap into off-screen DC
HGDIOBJ OldBitmap = SelectObject(hdcMem, MemBitmap);
// Erase background
HBRUSH hbrBkGnd = CreateSolidBrush(0x000000);
FillRect(hdcMem, &rect, hbrBkGnd);
DeleteObject(hbrBkGnd);
// Set font and color
HGDIOBJ hOldFont = SelectObject(hdcMem, Settings->hFont);
SetTextColor(hdcMem, Settings->crForeGnd);
SetBkColor(hdcMem, Settings->crBackGnd);
// Draw text
rect.right -= Settings->txt1XOffset;
DrawText(hdcMem, Settings->szText1, -1, &rect, DT_RIGHT);
rect.right += Settings->txt1XOffset - Settings->txt2XOffset;
DrawText(hdcMem, Settings->szText2, -1, &rect, DT_RIGHT);
// Blt the changes to the screen DC.
BitBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, hdcMem, 0, 0, SRCCOPY);
// Select old font
SelectObject(hdcMem, hOldFont);
// Done with offscreen DC and bitmap
SelectObject(hdcMem, OldBitmap);
DeleteObject(MemBitmap);
DeleteDC(hdcMem);
if (!wParam) EndPaint(Settings->hWnd, &ps);
}
The first text is printed fine, but the second one looks like this:
And here is the complete code without the double buffering: http://dl.dropbox.com/u/35314071/ScrollingTextClass.zip
And here is the complete code with the double buffering: http://dl.dropbox.com/u/35314071/ScrollingTextClass2.zip

Okay, I debugged the program (it's amazing what you can find if you just step through the code line by line), and at the point where you call BitBlt, you pass the parameters
BitBlt(hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, hdcMem, 0, 0, SRCCOPY);
Inspection of the values in the debugger shows that rect.right - rect.left is not the full size of the window but only part of it because you still have the value in rect.right left over from the line
rect.right += Settings->txt1XOffset - Settings->txt2XOffset;
You forgot to set rect.right back to its original value.
rect.right += Settings->txt2XOffset;

Related

Is creating Bitmap over and over in loop is necessary?

is CreateDIBitmap everytime in WM_PAINT is necessary? I thought if I give it apointer to my buffer
it will be updated - but it looks it copies the data. The CreateDIBitmap - takes a lot of performance. CreateBitmap a little less.. The code below works - is it possible to put CreateDIBitmap outside of the loop (MY_BUFFER is updated every frame) - I tried to put it outside but I didn't see the content of MY_BUFFER anymore, just the DrawText with FPS..
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC hdc_mem = CreateCompatibleDC(hdc);
HBITMAP hbm_mem = CreateDIBitmap(hdc, &bitmapheader, CBM_INIT, &MY_BUFFER, &bitmapinfo, DIB_RGB_COLORS); // < --- killing performance !!!
SelectObject(hdc_mem, hbm_mem);
// draw FPS
TCHAR tmp_fps_buff[16];
ZeroMemory(&tmp_fps_buff, sizeof(tmp_fps_buff));
swprintf_s(tmp_fps_buff, L"fps: %.3f", APP_fps);
SetTextColor(hdc_mem, RGB(255, 255, 255));
SetBkColor(hdc_mem, RGB(0, 0, 0));
SetBkMode(hdc_mem, OPAQUE);
DrawText(hdc_mem, tmp_fps_buff, -1, &APP_fps_rect, DT_SINGLELINE | DT_NOCLIP);
// ---
BitBlt(hdc, 0, 0, APP_requested_width, APP_requested_height, hdc_mem, 0, 0, SRCCOPY);
DeleteObject(hbm_mem);
DeleteDC(hdc_mem);
EndPaint(hWnd, &ps);
}
break;

BitBlt not updating WM_PAINT in Windows C++ API

I have the following function to draw a loaded bitmap to a window.
void OnPaint(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc;
BITMAP bitmap;
HDC hdcMem;
HGDIOBJ oldBitmap;
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc);
HBITMAP bmp = mainBitmap;
oldBitmap = SelectObject(hdcMem, mainBitmap);
GetObject(bmp, sizeof(bitmap), &bitmap);
x += 64;
RECT rect;
rect.left = x;
rect.top = 0;
rect.right = x+64;
rect.bottom = 64;
HBITMAP hBmp = CreateCompatibleBitmap(
hdc, // Handle to a device context
rect.right - rect.left, // Bitmap width
rect.bottom - rect.top // Bitmap height
);
BitBlt(
hdc, // Destination rectangle context handle
0, // Destination rectangle x-coordinate
0, // Destination rectangle y-coordinate
rect.right - rect.left, // Destination rectangle width
rect.bottom - rect.top, // Destination rectangle height
hdcMem, // A handle to the source device context
rect.left, // Source rectangle x-coordinate
rect.top, // Source rectangle y-coordinate
SRCCOPY // Raster-operation code
);
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
}
And I have the following image loaded into HBITMAP mainBitmap:
The image is drawn when the window opens successfully, and I see the first icon in the sprite bitmap (yellow grapple hook), but my issue is, when I press 'C' to re-paint the window, the image does not change to the next icon in the sprite image.
Things I Know
On initialization, x = 64;
Every time I press 'C', paint is called. (Confirmed in Visual
Studio Debugger)
x is incremented by 64 each time OnPaint is called.
Why is the graphic not changing?
Here is my WindowsProc function to handle the WM_PAINT message:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Try to call function InvalidateRect() to update region.

Off-screen drawing GDI+

I have a problem - I need to draw two png files, one on the other. When I do it usual way, there is a "blinking" effect (first image overdraws the second one for small time period). I use GDI+ library and my WM_PAINT handling looks like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint( hwnd, & ps );
displayImage(firstImage, hwnd);
displayImage(secondImage, hwnd);
EndPaint( hwnd, & ps );
break;
}
displayImage function:
void displayImage(HBITMAP mBmp, HWND mHwnd)
{
RECT myRect;
BITMAP bm;
HDC screenDC, memDC;
HBITMAP oldBmp;
BLENDFUNCTION bf;
GetObject(mBmp, sizeof(bm), &bm);
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0xff;
bf.AlphaFormat = AC_SRC_ALPHA;
screenDC = GetDC(mHwnd);
GetClientRect(mHwnd, &myRect);
if (mBmp == NULL)
FillRect(screenDC, &myRect, WHITE_BRUSH);
else
{
memDC = CreateCompatibleDC(screenDC);
oldBmp = (HBITMAP)SelectObject(memDC, mBmp);
AlphaBlend (screenDC, 0, 0, myRect.right,myRect.bottom, memDC, 0, 0, bm.bmWidth,bm.bmHeight, bf);
SelectObject(memDC, oldBmp);
DeleteDC(memDC);
ReleaseDC(mHwnd, screenDC);
}
}
Loading files to variables:
HBITMAP mLoadImg(WCHAR *szFilename)
{
HBITMAP result=NULL;
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(szFilename,false);
bitmap->GetHBITMAP(NULL, &result);
delete bitmap;
return result;
}
firstImage = mLoadImg(L"data\\img\\screen.png");
secondImage = mLoadImg(L"data\\img\\screen2.png");
I've heard that I should do a off-screen drawing. How should that look like?
You don't need all of that. You can use GDI+ directly:
static Gdiplus::Image *firstImage;
static Gdiplus::Image *secondImage;
case WM_CREATE: // or WM_INITDIALOG if it's dialog
{
firstImage = new Gdiplus::Image(L"data\\img\\screen.png");
secondImage = new Gdiplus::Image(L"data\\img\\screen2.png");
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
Gdiplus::Graphics gr(hdc);
gr.DrawImage(firstImage, 0, 0);
gr.DrawImage(secondImage, 0, 0);//<== this will draw transparently
EndPaint(hwnd, &ps);
return 0;
}
However, this code is still drawing 2 images back to back with possible flicker (like your original code). Use double-buffering in WM_PAINT so that only one BltBlt is done. Simply change to:
if (msg == WM_PAINT)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
HGDIOBJ oldbmp = SelectObject(memdc, hbitmap);
FillRect(memdc, &rc, WHITE_BRUSH);
Gdiplus::Graphics gr(memdc);
gr.DrawImage(firstImage, 0, 0);
gr.DrawImage(secondImage, 0, 0);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldbmp);
DeleteObject(hbitmap);
DeleteDC(memdc);
EndPaint(hwnd, &ps);
return 0;
}
As for the original code:
void displayImage(HBITMAP mBmp, HWND mHwnd)
{
HDC hdc = GetDC(mHwnd);
...
}
You should change the function declaration to void displayImage(HBITMAP mBmp, HWND mHwnd, HDC hdc) then you can pass the hdc directly from WM_PAINT
First, change displayImage to take the HDC and RECT from the caller instead of the HWND.
void displayImage(HBITMAP mBmp, HDC hdc, const RECT &myRect)
{
if (mBmp == NULL)
FillRect(screenDC, &myRect, WHITE_BRUSH);
else
{
BITMAP bm;
GetObject(mBmp, sizeof(bm), &bm);
HDC memDC = CreateCompatibleDC(screenDC);
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, mBmp);
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0xff;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(hdc, 0, 0, myRect.right, myRect.bottom, memDC, 0, 0, bm.bmWidth, bm.bmHeight, bf);
SelectObject(memDC, oldBmp);
DeleteDC(memDC);
}
}
Then, in the caller create a compatible DC and bitmap. These are your off-screen space for doing the compositing. Make the calls to displayImage with this new DC. This will compose the PNGs offscreen. Finally, blit the composed result to the actual window DC in one go.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT myRect;
GetClientRect(hwnd, &myRect);
// Create an off-screen DC for composing the images.
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmpMem = CreateCompatibleBitmap(hdc, myRect.right, myRect.bottom);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpMem);
// Compose the images to the offscreen bitmap.
displayImage(firstImage, hdcMem, myRect);
displayImage(secondImage, hdcMem, myRect);
// Blit the resulting composition to the window DC.
BitBlt(hdc, 0, 0, myRect.right, myRect.bottom,
hdcMem, 0, 0, SRCCOPY);
// Clean up the offscreen stuff.
SelectObject(hdcMem, hbmpOld);
DeleteObject(hbmpMem);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
break;
}
Finally, if you're still seeing a flash of the background color, see Pavan Chandaka's answer.
Handle "WM_ERASEBKGND" message by your self.
Actually before loading the second image two things happen.
WM_ERASEBKGND is triggered first to fill the image area with, whatever current windows background color is.
WM_PAINT to render action.
Documentation says to avoid blink/Flickr, provide a default handler for "WM_ERASEBKGND".
Below is the link, go to "A Control That Doesn't Flicker". You have an example too.
https://msdn.microsoft.com/en-us/library/ms969905.aspx

Double-buffering AlphaBlend rect with text overlay

I'm a little confused on how to double buffer this. I'm
not sure if I need to create another CreateCompatibleBitmap or CreateCompatibleDC and how to link it all.
This works as is but I don't think its double buffered right.
void __OnPaint(HWND hWnd, HDC _hdc = nullptr)
{
HDC hdc = _hdc;
PAINTSTRUCT paint;
RECT& rcClient = paint.rcPaint;
if (!_hdc)
hdc = BeginPaint(hWnd, &paint);
else
GetClientRect(hWnd, &rcClient);
if (hdc)
{
int width = rcClient.right - rcClient.left;
int height = rcClient.bottom - rcClient.top;
HDC hDCMem = CreateCompatibleDC(_hdc);
HBITMAP hBitmapMem = CreateCompatibleBitmap(hDCMem, width, height);
SelectObject(hDCMem, hBitmapMem);
Rectangle(hDCMem, 0, 0, width, height);
BLENDFUNCTION bfn;
bfn.BlendOp = AC_SRC_OVER;
bfn.BlendFlags = 0;
bfn.AlphaFormat = 0;
bfn.SourceConstantAlpha = 0x50;
AlphaBlend(hdc, 0, 0, width, height, hDCMem, 0, 0, width, height, bfn);
SetTextColor(hdc, RGB(255, 0, 0));
SetBkMode(hdc, TRANSPARENT);
DrawText(hdc, "Your text here", -1, &rcClient, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
//BitBlt(hdc, 0, 0, width, height, hDCMem, 0, 0, SRCCOPY);
DeleteDC(hDCMem);
DeleteObject(hBitmapMem);
}
if (!_hdc)
EndPaint(hWnd, &paint);
}
Also i found i have another problem with this.
i move my window in WM_TIMER, i call my __onpaint, problem im having is that it does not redraw it has something todo with the alphaBlend, it keeps what ever was under the window at the time of 1st draw, since it worked before i was using that
double buffering is to do all your printing and drawing on a temporary bitmap, that should be stored somewhere. the drawings on that bitmap can happen outside of WM_PAINT event (eg: on Adding items or selection change).
then on WM_PAINT event, the only thing you have to do is to project that bitmap to the window via BitBlt function or similar functions.
the way you are using AlphaBlend is wrong. AlphaBlend is used to draw images that have an AlphaChanel over an existing image as an overlay.

How to set pixel on mouse click in C++ using WinApi (GDI) in GUI window?

I'm trying to set pixel by mouse click, but nothing happens when I click. Here is part of my code.
First, I control window size changing in WM_SIZE.
Than, at first time when I want to set pixel by mouse I get window's width and height, then copy window's content to memory HDC and HBITMAP (in Store Window) (HBITMAP size equal to (width,height)). In fact, I copy to memory only clear window.
And than in any case I set pixel to memory DC. In next WM_PAINT message handling I'm drawing memory DC to screen.
.....
case WM_SIZE:
{
CheckWidthHeight();
break;
}
case WM_MBUTTONDOWN:
{
if (firstTimeDraw)
{
CheckWidthHeight();
StoreWindow();
firstTimeDraw = false;
}
SetPixel(memoryDC, LOWORD(lParam), HIWORD(lParam), RGB(0,0,0));
break;
}
case WM_PAINT:
{
RestoreWindow();
break;
}
.....
where my functions and variables is:
HDC memoryDC;
HBITMAP memoryBitmap;
int width = 0, height = 0;
bool firstTimeDraw = true;
void CheckWidthHeight()
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
width = clientRect.right - clientRect.left;
height = clientRect.bottom - clientRect.top;
}
//Copy real window content to memory window
void StoreWindow()
{
HDC hDC = GetDC(hwnd);
memoryDC = CreateCompatibleDC(hDC);
memoryBitmap = CreateCompatibleBitmap(hDC, width, height);
SelectObject(memoryDC, memoryBitmap);
BitBlt(memoryDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hDC);
}
//Copy memory windows content to real window at the screen
void RestoreWindow()
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
memoryDC = CreateCompatibleDC(hDC);
SelectObject(memoryDC, memoryBitmap);
BitBlt(hDC, 0, 0, width, height, memoryDC, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
What I'm doing wrong?
UPD:
A shot in the dark: You're handling the middle button click. Are you by any chance clicking on the left or right mouse buttons? :)
Ok. Now I use WM_LBUTTONUP or WM_LBUTTONDOWN. Nothing happens again.
UPD2:
When you change the memory DC, you'll also want to invalidate the part of the window that is affected so that Windows will generate a WM_PAINT message for it. InvalidateRect would be a good place to start.
I placed this code
RECT rect;
GetClientRect(hwnd, &rect);
InvalidateRect(hwnd, &rect, true);
before EndPaint. Nothing. Than I move it after EndPaint. Nothing.
In the WM_PAINT handler, you need to use a DC provided by BeginPaint and call EndPaint when you're done with it.
I do it in RestoreWindow().
I don't know yet what's the problem...
UPD3:
InvalidateRect() needs to happen in the WM_?BUTTONDOWN handler after the SetPixel (not in RestoreWindow())- it's what tells windows that you want to get a WM_PAINT in the first place.
Ok. I've done it before you wrote this message. Still don't work.
UPD4:
Thank you a lot, Remy! Thank you to all the rest. Now all right!!
Two things.
When you change the memory DC, you'll also want to invalidate the part of the window that is affected so that Windows will generate a WM_PAINT message for it. InvalidateRect would be a good place to start.
In the WM_PAINT handler, you need to use a DC provided by BeginPaint and call EndPaint when you're done with it.
When you call RestoreWindow() to draw the bitmap onscreen, you are wiping out your memoryDC variable that you used to draw the pixels with. The bitmap is still selected into the original HDC that you have now lost, and a bitmap cannot be selected into multiple HDCs at the same time (the MSDN documentation for SelectObject() says as much). So you are not actually drawing the bitmap onscreen at all.
There is no need to call CreateCompatibleDC() or SelectObject() inside of RestoreWindow() because you already have the bitmap and memory HDC set up inside of StoreWindow(), so they use them as-is instead.
Try this:
HDC memoryDC = NULL;
HBITMAP memoryBitmap = NULL;
int width = 0, height = 0;
void CheckWidthHeight()
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
width = clientRect.right - clientRect.left;
height = clientRect.bottom - clientRect.top;
}
void StoreWindow()
{
HDC hDC = GetDC(hwnd);
memoryDC = CreateCompatibleDC(hDC);
memoryBitmap = CreateCompatibleBitmap(hDC, width, height);
SelectObject(memoryDC, memoryBitmap);
BitBlt(memoryDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hDC);
}
void RestoreWindow()
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
if (memoryDC)
BitBlt(hDC, 0, 0, width, height, memoryDC, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
...
case WM_SIZE:
{
CheckWidthHeight();
break;
}
case WM_LBUTTONDOWN:
{
if (!memoryDC)
StoreWindow();
if (memoryDC)
{
SetPixel(memoryDC, LOWORD(lParam), HIWORD(lParam), RGB(0,0,0));
RECT rect;
rect.left = LOWORD(lParam);
rect.top = HIWORD(lParam);
rect.right = rect.left + 1;
rect.bottom = rect.top + 1;
InvalidateRect(hwnd, &rect, TRUE);
}
break;
}
case WM_PAINT:
{
RestoreWindow();
break;
}
...