The MASM Forum

General => The Workshop => Windows Graphics Programming => Topic started by: NoCforMe on April 25, 2025, 11:54:11 AM

Title: Moving things around the screen w/GDI
Post by: NoCforMe on April 25, 2025, 11:54:11 AM
Another project with problems.

I'm trying to learn how to move things around the screen with the mouse, using GDI.
Previously I was moving child controls around, for my DialogGen on-screen dialog editing program, so I pretty much know how to do that (although there are still problems with that scheme).

This time I'm moving other things around. Mainly bitmaps at this point.
I'm able to move them pretty well, but there are problems. Mainly flicker. Actually pretty horrible flicker.

(Use the attached program and move the NPN transistor around and see what happens.)

For the time being I'd like to keep this on a conceptual level and proceed without posting any code. (I'll be happy to do that later.) This isn't so much a problem with coding as it is with design, and with understanding how the OS works with graphics and what its limitations are.

What I'm doing is this:

OK, now that I have a memory DC, I'm doing this:


As you can see this works, but at a very high price. If you move the mouse at any speed above s-l-o-w, it flickers badly.

Any ideas here? I'm pretty sure I'm doing what I'm doing correctly, so far as coding goes. In other words, I don't think this is a matter of bugs. More a case of poor use of resources.

Am I doing too much moving of stuff between DCs? is that what's causing the flicker?
I was hoping that by only copying a small part of AltDC to the display for each mouse move that I'd be avoiding too much processing.

Should I be doing some kind of double buffering? (Not even sure how that works, to be honest.)

Any better way to do what I'm trying to do here?
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 25, 2025, 12:04:17 PM
For starters, you have a gdi leak...

Also, double buffering would indeed help.

untitled.PNG

ALso that white rectangle with border appears sometimes when moving the transistor around.
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 25, 2025, 12:08:50 PM
Quote from: zedd on April 25, 2025, 12:04:17 PMFor starters, you have a gdi leak...
No doubt. Let's not worry about that for the moment. That's not the problem.

QuoteAlso, double buffering would indeed help.
I'm going to read up on it next thing.

Well, a quick read showed ... basically the code I have now.

Question: Does using double buffering mean that I draw everything to a separate buffer (my AltDC) and then copy that entire thing over to the display DC? Because I'm not doing that exactly.
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 25, 2025, 12:13:54 PM
This is how I would do it... and so far works for me.
If you have the client area already from GetClientRect, you can use that here instead of ps.rcPaint...

        .elseif uMsg == WM_ERASEBKGND          ; adding this helps greatly with flicker issues
          mov eax, 1
          ret
        .elseif uMsg == WM_PAINT
          invoke BeginPaint, hWin, addr ps
          mov hDC, eax                          ; window client area DC
          invoke CreateCompatibleDC, hDC
          mov memDC, eax                        ; memory DC

          invoke CreateCompatibleBitmap, hDC, ps.rcPaint.right, ps.rcPaint.bottom
          mov hBmp, eax                        ; compatible bitmap handle
          invoke SelectObject, memDC, hBmp
          mov hBmp_old, eax

          ;; ###########################################################

          ;; in this example do all of your drawing here to memory DC (memDC) not to window DC (hDC)



          ;; ###########################################################
          ;; bitblt memory Dc to window DC
          invoke BitBlt, hDC, 0, 0, ps.rcPaint.right, ps.rcPaint.bottom, memDC, 0, 0, SRCCOPY
          invoke SelectObject, memDC, hBmp_old
          invoke DeleteObject, hBmp
          invoke DeleteDC, memDC

          invoke EndPaint, hWin, addr ps
Title: Re: Moving things around the screen w/GDI
Post by: sinsi on April 25, 2025, 12:27:49 PM
Quote from: NoCforMe on April 25, 2025, 12:08:50 PMQuestion: Does using double buffering mean that I draw everything to a separate buffer (my AltDC) and then copy that entire thing over to the display DC?
Yes, everything.

I get artifacts when the bitmap is dragged past the top or left
Untitled.png
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 25, 2025, 12:46:40 PM
Quote from: sinsi on April 25, 2025, 12:27:49 PM
Quote from: NoCforMe on April 25, 2025, 12:08:50 PMQuestion: Does using double buffering mean that I draw everything to a separate buffer (my AltDC) and then copy that entire thing over to the display DC?
Yes, everything.

I get artifacts when the bitmap is dragged past the top or left
Yes, shoulda warned you 'bout that. There's no code to check for boundary conditions (yet). Makes a mess.

So maybe I should concentrate on "everything"?
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 25, 2025, 12:47:50 PM
I will also add that however you are selecting the transistor and moving it around, that part indeed is working nicely. Congrats.  :thumbsup:
I had tried something similar years ago, but with piss poor results.  :tongue:

I think I could do something similar now, if needed, though -with better results.   :smiley:
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 25, 2025, 12:52:00 PM
Quote from: NoCforMe on April 25, 2025, 12:46:40 PMSo maybe I should concentrate on "everything"?
Work on getting the double buffering working first. Once done, then you can do the boundary checks, and handle the resource issues (source of gdi leaks), imo.
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 27, 2025, 10:44:09 AM
Progress!

After fixing a basic mistake, you can see that moving that bitmap around is now very smooooove. All because of buffering.

(Just a side note: I'm not using the term "double-buffering" because I don't even know what that really means: what's "double" about it? I'm actually using 3 buffers here. See below.)

Here's the scheme I'm using:

Buffering.gif

I create 3 additional DCs:

The source DC holds a rendering of everything except the "object" being moved (hereafter referred to as the OBM). It's a baseline image used for restoring parts of the image overwritten when moving the OBM. It's only created once, when mouse movement first starts, so that saves the expense of redrawing everything for each mouse move.

The work DC is, as the name implies, a work area for drawing into.

Both the source and work DCs are sized to the display DC (by creating a bitmap from the display DC using CreateCompatibleBitmap() and selecting that into the DC).

When the OBM is moved (when we get a WM_MOUSEMOVE message), the first thing to do is to erase the OBM, including its selection outline, at the "old" position (where it currently is on the display). This is done by copying that part of the source DC to the work DC.

With the underlying image restored to the work DC, the selection outline and the bitmap itself are drawn into the work DC. This can all now be copied to the display DC. The trick here is to make the copy rectangle equal to the union of the "old" and "new" display rectangles, so that the old left-behind parts of the OBM are erased and the OBM shown in the "new" position.

It all works pretty well. However, as you can see if you run the demo, there are still some leftover parts that leave garbage on the display. (The interesting thing is that if you play around with this long enough, it stops leaving stuff behind. I don't understand why.) I'm guessing this is because of an "off by one" error or some such.

Anyhow, play with it. It's so much smoother than before: no flickering that I can see.

BTW, one thing I learned is that Windoze definitely doesn't like it when you select the same bitmap into two DCs. So when I used CreateCompatibleBitmap() from the display DC, I had to do it twice to give me two separate bitmaps to select into the source & work DCs, to make them the same size as the display.
Title: Re: Moving things around the screen w/GDI
Post by: sinsi on April 27, 2025, 11:53:09 AM
Quote from: NoCforMe on April 27, 2025, 10:44:09 AMsaves the expense of redrawing everything for each mouse move.
Have you tried? Sometimes fiddling around is slower.

If it's only one thing moving at a time, you could make a bitmap when starting the drag and use it as a background brush.
Then you just need to paint the background to the memory DC with the brush, paint the dragged object and blit to the window DC.

The width of the artifacts depends on the mouse speed, maybe mouse acceleration is skewing measurements?
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 27, 2025, 01:01:30 PM
I am not sure if my finger slipped off the left mouse button while moving the transistor (its possible), but at one point I had two transistors on the screen at the same time. I screen captured it.
(https://i.postimg.cc/fTNtDkMW/untitled.png)

I could not however recreate that error.

Other artfacts:
(https://i.postimg.cc/fRHfVYbf/untitled.png)

No source?  :sad:
Let the source be with you... and with us, too.  :biggrin:
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 27, 2025, 01:06:40 PM
Yeah, yeah, the artifacts.
It's interesting that they come from the corners of the bitmap. That oughta tell me something.

@sinsi: that idea of using a background brush is intriguing, but I'm not sure I understand it.
Could you explain it a little bit more? Are you suggesting using the image of everything except the OBM (object being moved) as a brush?
Title: Re: Moving things around the screen w/GDI
Post by: sinsi on April 27, 2025, 01:25:46 PM
Quote from: NoCforMe on April 27, 2025, 01:06:40 PMAre you suggesting using the image of everything except the OBM (object being moved) as a brush?
Yep. Since everything else is static, you could draw it once (to a bitmap or brush) then use it as the background.
 - mouse down -> create bmp then optionally brush (the bitmap can be deleted if you use a brush)
 - mouse move -> double-buffer draw - fill memDC background with it, draw OBM to memDC then BitBlt memDC to winDC
 - mouse up   -> delete bitmap/brush
You won't have any gaps, so no artifacts.

I've never actually used a brush from a bitmap but it seems easy enough (provided you have a bitmap) with CreatePatternBrush.
It looks like a brush is easier to paint with, but a bitmap should still be OK using BitBlt.
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 27, 2025, 02:37:57 PM
Sinsi, your explanations about using background brushes and such will have to wait for another day.

My current motto: Simplify, simplify, simplify.

Check out current iteration. Totally smoooove moving, and no garbage left behind on the screen.

I totally eliminated all the "erasing the leftover bits from the last drawing" stuff.
Now all I'm doing is this:

Even with the slightly increased overhead of copying the whole DC every time, it's still smooth.

Sometimes the simpler solution is the best.
Title: Re: Moving things around the screen w/GDI
Post by: sinsi on April 27, 2025, 02:46:13 PM
OK, that's basically what I said  :biggrin:
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 27, 2025, 02:53:39 PM
It is much better now.

But there are issues when bitmap.x or bitmap.y is outside of the client area. The transistor gets placed, but is immovable after that placement.

(https://i.postimg.cc/76v947XP/untitled.png)

(https://i.postimg.cc/tTh54Tm3/untitled.png)

When can we see the code?
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 27, 2025, 03:23:03 PM
For those interested, code is attached.
Yes, selection is still imperfect; still no boundary checking being done. (Which is surprising, since the display code couldn't care less about that, but it does prevent re-selection if you move the thing outside the window.)

Here's the current, simplified moving scheme:

Buffering.gif

So this is pretty much done as a proof of concept.
There are issues to be considered. One is this: if this is used in a graphics program with lots of objects on-screen, then you need to deal with the Z-axis problem.

As it is, any "object" you move is going to end up on top of everything else. But that isn't always what you want if you have objects that are in a certain Z-order.

Easy enough to fix, though: when done moving, just redraw everything in the proper Z-order.
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 27, 2025, 03:31:47 PM
Quote from: sinsi on April 27, 2025, 02:46:13 PMOK, that's basically what I said  :biggrin:

Yes. You certainly deserve credit for that.
Title: Re: Moving things around the screen w/GDI
Post by: sinsi on April 27, 2025, 04:09:00 PM
Quote from: NoCforMe on April 27, 2025, 03:23:03 PMwhen done moving, just redraw everything in the proper Z-order.
I imagine that's what happens anyway, draw the final screen on mouse up.

Are you capturing the mouse or using down/move/up?
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 27, 2025, 04:12:16 PM
Quote from: sinsi on April 27, 2025, 04:09:00 PMAre you capturing the mouse or using down/move/up?
The source is in #16...
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 27, 2025, 04:26:38 PM
Quote from: sinsi on April 27, 2025, 04:09:00 PMAre you capturing the mouse or using down/move/up?

Good question; no, not using mouse capture. It didn't seem to be needed, although it might solve the drag-the-thing-partially-off-window problem.

In DialogGen I found I had to use mouse capture to make moving things around work properly.
Further research needed.
Title: Re: Moving things around the screen w/GDI
Post by: sinsi on April 27, 2025, 04:55:51 PM
After a quick look, just one thing. With WM_LBUTTONDOWN the mouse coordinates in lParam are signed words, so you should be using movsx and the signed jumps (JL rather than JB).
It doesn't matter nearly all of the time but you will eventually have it bite you in the arse  :biggrin:

I'll go through it in more detail once my visitors (Jack and Mary) have departed :badgrin: but it looks good :thumbsup:
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 28, 2025, 12:44:19 AM
After assembling the source from #16 the program shows me no signs of any artifacts. No obvious flickering either.  :thumbsup:

I did however change the main window style to enable minimizing/maximizing and resizing the window by dragging window edges. Of course severe gdi leaks occurred when adjusting the main window. But you already know about that.

Minimal gdi leaks otherwise, but still present. Also already known.

It seems to be getting much closer to "production ready" in any case.  :thumbsup:

One thing though. How easy/hard would it be to place the transistor, and deslect it upon releasing the left mouse button, instead of having to click outside of the transistors RECT to deselect it?

Another possible bug...
Added later: Sometimes the transistor stays bound to the mouse cursor position even after releasing the left mouse button... Not often, and cannot determine if is at certain positions only or just randomly. Once it happens though, the transistor can no longer be deselected, no matter the state of the left mouse button - and the transistor continues to follow mouse position.

more later upon further testing... (possible cause) If the left mouse button is double clicked while inside the transistorst RECT (as opposed to pressed and held while moving mouse)  that sometimes happens.
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 28, 2025, 02:39:38 AM
OK, this version solves the "stranded at the border" problem.
It was an easy fix (and @sinsi, our minds ran in the same rut here):
It was a matter of using signed instead of unsigned comparisons in my hit-testing code (in the WM_LBUTTONDOWN handler:

Old way (bad):
; Hit testing:
CMP EAX, CurrObjX
JB noHit
CMP EDX, CurrObjY
JB noHit
MOV ECX, CurrObjX
ADD ECX, BmpW
CMP EAX, ECX
JA noHit
MOV ECX, CurrObjY
ADD ECX, BmpH
CMP EDX, ECX
JA noHit

; Hit!

New way (good):
CMP EAX, CurrObjX
JL noHit
CMP EDX, CurrObjY
JL noHit
MOV ECX, CurrObjX
ADD ECX, BmpW
CMP EAX, ECX
JG noHit
MOV ECX, CurrObjY
ADD ECX, BmpH
CMP EDX, ECX
JG noHit

There's still that occasional problem of the mouse getting "stuck" to the OBM. Further research required here.

I tried mouse capture, but it led to some weird problems, like the OBM completely disappearing.
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 28, 2025, 03:09:21 AM
Quote from: NoCforMe on April 28, 2025, 02:39:38 AMThere's still that occasional problem of the mouse getting "stuck" to the OBM. Further research required here.
Well shucks. But you are making some progress at least. I applaud your persistence.
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 28, 2025, 03:13:18 AM
Quote from: zedd on April 28, 2025, 03:09:21 AM
Quote from: NoCforMe on April 28, 2025, 02:39:38 AMThere's still that occasional problem of the mouse getting "stuck" to the OBM. Further research required here.
Well shucks. But you are making some progress at least. I applaud your persistence.

Well, that's actually the complicated part of this whole scheme: the sequencing of events.
You need to define what state the code is in, from selecting an object by clicking on it to starting to move it by dragging the mouse. That's the main problem I'm trying to solve here; my DialogGen program suffers some of the same problems due to incorrect handling of state.

I figure I'm about 75% there, with another 75% to go ...
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 28, 2025, 04:03:48 AM
Quote from: NoCforMe on April 28, 2025, 03:13:18 AMI figure I'm about 75% there, with another 75% to go ...
:thumbsup:
Wait a minute! 75+75=150.

Does that mean it will end up 50% better than originally planned?  :biggrin:
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 28, 2025, 04:34:58 AM
Quote from: zedd on April 28, 2025, 04:03:48 AM
Quote from: NoCforMe on April 28, 2025, 03:13:18 AMI figure I'm about 75% there, with another 75% to go ...
:thumbsup:
Wait a minute! 75+75=150.

Does that mean it will end up 50% better than originally planned?  :biggrin:

You mean you never heard anyone say "it's 90% done with 90% to go"?

It means it will take at least 50% longer to get anywhere than hoped for.
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 28, 2025, 05:58:27 AM
Quote from: NoCforMe on April 28, 2025, 04:34:58 AMYou mean you never heard anyone say "it's 90% done with 90% to go"?
Nope, never heard of it.
QuoteIt means it will take at least 50% longer to get anywhere than hoped for.
Don't be so hard on yourself.
I would have put this in my "unfinished" pile already. I lack persistence and patience.
Title: Re: Moving things around the screen w/GDI
Post by: TimoVJL on April 28, 2025, 06:05:51 AM
Quote"it's 90% done with 90% to go"
does that mean, that there is still 9% left  and 0.9 % abandoned and rest is tolerance :biggrin:
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 28, 2025, 06:08:51 AM
Quote from: TimoVJL on April 28, 2025, 06:05:51 AM
Quote"it's 90% done with 90% to go"
does that mean, that there is still 9% left
90% of the remaining 10% = 9%...
I had to think about that for a moment.  :tongue:
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 28, 2025, 06:29:17 AM
Quote from: TimoVJL on April 28, 2025, 06:05:51 AM
Quote"it's 90% done with 90% to go"
does that mean, that there is still 9% left  and 0.9 % abandoned and rest is tolerance :biggrin:

You're overthinking this.
How can I put it? It's like "what you think is the light at the end of the tunnel is the headlight of a train barreling towards you".
Title: Re: Moving things around the screen w/GDI
Post by: TimoVJL on April 29, 2025, 12:52:21 AM
Quote from: NoCforMe on April 28, 2025, 06:29:17 AM
Quote from: TimoVJL on April 28, 2025, 06:05:51 AM
Quote"it's 90% done with 90% to go"
does that mean, that there is still 9% left  and 0.9 % abandoned and rest is tolerance :biggrin:

You're overthinking this.
How can I put it? It's like "what you think is the light at the end of the tunnel is the headlight of a train barreling towards you".
No, You're overthinking.
That 90% and 90% rule is simple, as it means, that hardest part is ahead ?
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 29, 2025, 05:12:29 AM
Quote from: TimoVJL on April 29, 2025, 12:52:21 AMThat 90% and 90% rule is simple, as it means, that hardest part is ahead ?

Bingo. You got it.
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 29, 2025, 02:41:26 PM
OK, here's another version. Probably the last, as this is about as far as I want to take this project.

Check it out: smoooove moves, and the object selection now makes sense; if you let go of the left mouse button the object doesn't "stick" anymore, and you can select any other object. (Everything can now be moved, not just the bitmap.)

What I really want to concentrate on is moving child windows, like I do in my DialogGen. That's a little trickier in some ways, but also easier in some other ways.
Title: Re: Moving things around the screen w/GDI
Post by: zedd on April 30, 2025, 02:41:24 AM
Quote from: NoCforMe on April 29, 2025, 02:41:26 PMOK, here's another version. Probably the last, as this is about as far as I want to take this project.
I have one suggextion. Use the status bar to display the selected objects x and y coordinates.
I would also deselect the objects on WM_LEFTBUTTONUP, but thats just me.
Title: Re: Moving things around the screen w/GDI
Post by: NoCforMe on April 30, 2025, 04:56:39 AM
Quote from: zedd on April 30, 2025, 02:41:24 AM
Quote from: NoCforMe on April 29, 2025, 02:41:26 PMOK, here's another version. Probably the last, as this is about as far as I want to take this project.
I have one suggextion. Use the status bar to display the selected objects x and y coordinates.

OK, but that's icing on the cake at this point. Keep in mind that this is only a proof-of-concept demo.

QuoteI would also deselect the objects on WM_LEFTBUTTONUP, but thats just me.

That's certainly a valid choice, and falls under the heading of tailoring user-interface behavior. I came up with a scheme that makes sense to me: others might want things to work differently.

I'm going to make a post here soon that explains how to figure that kind of stuff out.