Image Layers in Windows

I wish to create a dual-layer image in a window. The bottom (underlying) layer would be an image loaded from file, e.g. a photograph. The top layer would be transparent or semi-transparent. This would serve as a drawing surface. It must also be possible to erase lines and shapes drawn on the top layer, without affecting the image on the bottom layer. Think "tracing paper".

Does anyone know the best way to accomplish this?

As a note, I have already tried painting the bottom image in WM_ERASEBKGND. Though this works, sortof, the behavior of the window becomes erratic and bizarre.
I haven't done this myself in Windows (only OpenGL), but this might help you?
https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-drawing-with-opaque-and-semitransparent-brushes-use
Thanks Ganado. That's not exactly it. So far, the best solution that comes to mind, is to create two child windows. The underlying window, the one displaying the photo, would be "static". The top window would have a transparent background. When a line, shape, or outline (on the top window) is modified, the bounding rectangle of that shape is invalidated, and the shape is redrawn using the new coordinates.

This seems like a rather clunky and overly-complicated way to do this.
eh? keep the original in a memory buffer, and the victim in another. Draw all you want on the victim, and if the user mashes 'undo' or 'delete this part' or whatever, just pull the pixels from the original back over in the same locations that you touch to delete it. Its kinda brute force, but your computer can draw that image 80+ times a second. I think its ok.

You can make actual real layers, of course, but that seems useless for most real work? The only point I can see to it would be if you wanted to mirror/rotate/move/screw with the drawing layer only. In which case, you will have to do exactly that.. define an alpha channel using RGBA instead of RGB pixel formats and anything with the A set to nonzero is drawn on, anything zeroed is see thru, and render the surface over the original. That is more work, but if you need more features, its what must happen.
Last edited on
Thanks Jonnin. Still, can anyone explain how to do double buffering in GDI+?

The transparent-window idea seems like the most feasible. That way, both images can be scrolled in-sync. Unfortunately, all of the documentation that I have read, claims that transparency can't be done with a child window, using Windows 7 (which is what I have).

Yet, I have seen transparent layers used in photo editing and "drag-and-drop", at-least since Win 95 (if not earlier). So, there must be a relatively simple way to do this.
Thanks Furry Guy. Unfortunately, I just can't make sense of this double-buffering thing. How can I use it for scrolling? How can I draw a shape over the background (reference) image, and then move or modify that shape, without affecting the background image? More specifically, how can I invalidate a single rectangle in the larger image. I keep hitting dead-ends. I know that this can be done. Why can't I do it?

On Direct2D, I tried to learn it. But, the code was so insane, that I gave up. At least, with GDI+, loading and displaying an image is quite simple.

Please forgive any curtness I might be expressing. I'm just getting really frustrated right now.
if it helps, here is a theoretical example of what I am trying to do. Is anyone familiar with Petzold's "Bezier" program? It creates a Bezier Curve that can be manipulated with a mouse. For reference, here is Petzold's code:

https://github.com/recombinant/petzold-pw5e/blob/master/Chapter%2005%20Basic%20Drawing/04%20Bezier/Bezier.c

I wish to do something similar, but on top of a background reference image. As the curve is manipulated, the background image must remain unaffected.
I just can't make sense of this double-buffering thing. How can I use it for scrolling?
What do you mean by 'scrolling'?

Actually you can create as many memory dc's as you want. As I understand it the only problem you currently have is erasing the parts which are uncovered. So create two dc's: Fill both with the image. One for the drawing and the other one for ereasing. You can use BitBlt(...) to erase the unwanted drawings.
Y'all are not gonna believe this! After three days of scouring the internet, and pulling my hair out, I stumbled across the solution by pure accident---all, from a mistyped line of code. The solution proved to be so simple and obvious, that it was ridiculous. Without going into long details of how I made my accidental discovery, here is what I found:

Using GDIPlus, I had loaded a JPEG image into memory. In WM_PAINT, I used this code to display the image:

 
graphic1.DrawImage(image1, Key->xImgOrg, Key->yImgOrg, cxSize, cySize);


I decided to draw a line on the image, just to see what would happen. For the line, I used the standard GDI functions, rather than GDIPlus. Here is how that bit of code looked now:

1
2
3
4
5
6
graphic1.DrawImage(image1, xImgOrg, yImgOrg, cxSize, cySize);

SelectObject(hdc, GetStockObject(DC_PEN));                      //  Test Line
SetDCPenColor(hdc, RGB(255, 0, 0));
MoveToEx(hdc, xImgOrg+100, yImgOrg+100, NULL);
LineTo(hdc, xImgOrg+1100, yImgOrg+1100);


The line was drawn, just as I wanted. It even scrolled with the image. I then had WM_PAINT draw the image again, over the line. This is how that bit of code looked now:

1
2
3
4
5
6
7
8
graphic1.DrawImage(image1, xImgOrg, yImgOrg, cxSize, cySize);

SelectObject(hdc, GetStockObject(DC_PEN));                      //  Test Line
SetDCPenColor(hdc, RGB(255, 0, 0));
MoveToEx(hdc, xImgOrg+100, yImgOrg+100, NULL);
LineTo(hdc, xImgOrg+1100, yImgOrg+1100);

graphic1.DrawImage(image1, xImgOrg, yImgOrg, cxSize, cySize);


The line was erased! It would appear, that GDIPlus double-buffers images by default. No wonder my attempts at double-buffering were producing such erratic and crazy results. I was fighting something that was already there.

I did some experimentation, to see how practical this little trick might be. With a little code tweaking, I found that "InvalidateRect()" could also be used to erase all or part of the line---as well as redraw it in a new location. The only tricky part came in defining the coordinates. The drawing functions use the Image coordinates, while "InvalidateRect()" uses the coordinates of the window.

Anyway, that's my story.
Well, GDI+ is known for being slow. So it's probably better to use GDI directly.

What are you doing now is drawing everything when there is a change. That will lead to flicker when the changes are too fast. To avoid the flicker you need double buffering. But you can see how far you get with this approach.

I found that "InvalidateRect()" could also be used to erase all or part of the line
InvalidateRect() does not erase anything. You just draw the whole thing in the new state.

The only tricky part came in defining the coordinates.
Why are they different?
Fortunately, the graphics I'm using work fast enough for the simple application that I am creating. The image doesn't flicker, as I have trapped (disabled) the WM_ERASEBKGND command. The graphics aren't the center of my idea---merely a visual reference. These graphics are also a lot simpler and easier to implement, than anything in the DirectX family. If this project proves feasible, I may consider using the more advanced graphics. But, for the moment, they do what I need to do.

Yes, "InvalidateRect()" merely draws things in the new state---erasing the old state. Or, if I tell it that there is nothing in the new state, it will erase everything, leaving only the background image.

As for why the coordinates are different between drawing functions (like "LineTo") and Windows functions (like "InvalidateRect")---only Microsoft can really answer that question. I can though, describe how they are different. All drawing functions use the upper-left corner of the image (page) as their origin. Windows functions (like "InvalidateRect") use the upper left corner of the window as their origin, regardless of how the image has been scrolled.
Topic archived. No new replies allowed.