Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Bitmap as .backgroundImage vs bitmap as .Image 1

Status
Not open for further replies.

rmusgrove

Programmer
Mar 12, 2003
43
US
I have a couple questions, actually. I have an application where an image is used as a "canvas", and some other little images get added on top of it and dragged around on it. For the canvas image, I've tried putting it in a PictureBox as both a .BackgroundImage and a .Image, and I've also tried it as a .BackgroundImage for a Panel. Using this image as a .BackgroundImage for either control just doesn't work. The redraw when things happen above it is so slow ... it's horrible (screen flicker, etc). Does this make sense? The only way I'm able to make it work is as a PictureBox .Image. It's basically ok, except for the following: The images I drag around on it are transparent .GIFs, also set up as .Images in little PictureBoxes whose .Parent is the "canvas" PictureBox, with a transparent background color. When these images get moved around on the canvas the transparency of them somehow affects the canvas image in a really weird way. It kind of distorts it as it moves across it so that it has the effect of being like a weird piece of glass, which is transparent, but which is bending the light coming through it. I'm sure it's a kind of refresh issue with this canvas, and it's actually an interesting effect (it's sort of like the alien in the movie Predator when he was "invisible"), but I can't have it in the release version of the application. Does this make sense to anyone? Is there any kind of solution to this? GDI+ maybe? I feel like I've tried everything short of that, but it seems like an unneccessary level of complexity to resort to for something as seemingly simple as this. Thanks for any suggestions you might have.
 
I would use GDI+, and do some double buffering to try and eliminate the flickering. Its not that complex.
 
River,
Thanks. Actually, I guess that when I set up these images as picturebox .images I *am* using GDI+, though not manually. I've tried the usual call for setting up the default double buffering:

Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or_
ControlStyles.UserPaint Or _
ControlStyles.DoubleBuffer, True)

but the effect is the same. What I think is happening is that the whole rectangle defined by the image I'm dragging across the canvas it is being redrawn, and I'm seeing the redraw effect through the transparent pixels in the small "dragged" image. Is there a specific way to tell the form it shouldn't redraw pixels that were covered by transparent pixels ... or something? Should I work with this at a deeper GDI+ level? If so, can you give me a hint? Do you see the simplicity of what I'm trying to accomplish here? I just have this large picturebox with a complex image in it. On top of it I have some little picture boxes containing images with a transparent color. I just want to be able to maneuver them on the complex background by having them follow the mouse when the left button is down. It all works fine except for this "glassy" effect when I'm dragging them. The whole background isn't repainting, or anything like this (as long as I set it up as a .Image and not a .BackgroundImage), just the the rectangle behind the smaller image(s). I think all that *really* needs to be repainted are the pixels that were covered by the non-transparent pixels in the small image. Can it be done? Am I way off-base ... or out of my mind? Thanks.
 
I usually implement double buffering manually with bitmaps and what not.

I am by far no where near an expert on graphics. But I don't think you want just a portion of the image to be redrawn. I think you DO want the whole image to be redrawn. The steps I would use in pseudocode would be something like:

Dim g As Graphics = Graphics.FromImage(InMemoryBitmap)
g.DrawImage(YourBackGroundImage,0,0)
g.DrawImage(AForeGroundImage,LocationX,LocationY)
'Repeat for other images.
ControlsGraphicsObjectPassedIntoRoutine.DrawImage(InMemoryBitmap)

Here is a good double buffering article. The site is very helpful as well.
 
RiverGuy,

Yeah, I'd read that article backwards and forwards already. Thanks, though. I'm just having a tough time applying it to my situation. The article really isn't addressing the issue of transparency, among other things. In the past couple hours I've learned a few things you and others might find interesting. First, there does not really seem to be such a thing as "transparency" in this context. GDI+ implements a transparent background color in a very retarded way, not by making pixels "see-through", but by imposing the pixels of the image in the "background" on the image "above". So the "glassy" effect I see when dragging my images across the "canvas" is NOT the background redrawing, as I'd surmised, but the small image repainting its own background, which it's importing somehow from its parent. This is really a horrible mess. There is no such thing as true transparency in a pictureBox .Image. So, even though an image placed on top can look more or less transparent with respect to its "parent" (unless it's in motion), it's not transparent with respect to another image that might be placed near or partially on top of it.

Then, I fiddled for a bit with implementing manual GDI+ code for drawing a graphic and learned that you can't do a

g = Graphics.FromImage("myimage.GIF").

You get an error about not being able to make a Graphics from an indexed image...

All this stuff was ok in VB 6... Unbelievable... I knew I was going to have to rewrite a lot of this particular application to get it to .NET, but I never believed it would come to this....
 
In case anyone's following this, a final update:

I got this working the way I want. Two things were involved, both kinda sorta related to double-buffering principles, but not exactly. First, I solved the issue of the small image background redraw ("glassy effect") by changing code that looks like this:

If e.Button = MouseButtons.Left Then _
myPictureBox.location = New Point(newX, newY)

to this:

If e.Button = MouseButtons.Left Then
dim b as Bitmap = theTransparentBitmapIveBeenUsing
b.MakeTransparent() ' Make it transparent AGAIN
myPictureBox.Image = b ' reassign it to my image
myPictureBox.location = New Point(newX, newY)
end if

I just stumbled on this. Something about redoing the (pseudo) transparency at this point, just before the image is moved, causes the background (understand that "transparent" backgrounds in .NET/GDI+ are NOT "transparent"; they have a "background" equal to whatever parent pixels are behind their rectangle, so they "appear" transparent ... sometimes) of it to be painted at a more appropriate point, or to take advantage of the basic double-buffering I set up for the whole form. Whatever the case, it works.

The other issue I solved was also related to this psuedo transparency, and was much more complicated to fix. Because "pseudo-transparent" images aren't transparent, when one picturebox with a such an image overlaps another, it's really obvious. You can clearly see they have backgrounds when this happens, because the background of the one in front obscures the one beneath. It happens because the image of these little pictureboxs isn't part of the parent background. The solution to this is to "burn them in". For this I had to use GDI+ drawing calls and so forth, which I was hoping to avoid. The principle is like this: First, use a double-buffering "_backbuffer". Make a copy of an "empty canvas" and hang on to it. When a tranparent background picturebox is created on the parent (background) picturebox, DRAW a copy of it right on the background, at the same location where transparent background picturebox is placed. It goes like this:

dim g as graphics
g = Graphics.FromImage(backgroundPictureBox.Image)
g.DrawImage(myPictureBox.Image, leftPos, topPos)
g.Dispose()

So now I effectively have 2 images of myPictureBox.Image on the main background: The one in the pictureBox control, and one I've drawn right on the background. If I was to drag the picturebox without accounting for that, you'd see the one I "burnt in". But it needs to be there, so that if I overlay it with another pictureBox image, the pixels that make it up will show through to the "pseudo transparent" background of that 2nd image.

Anyway, it's no good to leave that drawn one there if I move the pictureBox, and that's where the _backbuffer comes in. It has the pixels that were in that location before I drew on them. In the mouseDown for that pictureBox I just have to replace the new pixels with the old ones, like so:

Dim bFrom As New Bitmap(_backbuf)
Dim bTo As New Bitmap(backgroundPictureBox.Image)
Dim g As Graphics = Graphics.FromImage(backgroundPictureBox.Image)
Dim r As New Rectangle(leftPos, topPos, w, h)
g.DrawImage(bFrom, r, r, GraphicsUnit.Pixel)

That's it. This code paints the original background, saved in _backbuf, back on to the "live" picturebox image in the rectangle covered by the pictureBox that was mouseDown'd.

Now, these principles could be really basic for game programmers and animation specialists ... but I'm not one. VB 6 let me accomplish all this with no code by simply dragging around transparent .GIFs in Image controls.
It took me over 12 hours to simulate this very basic behavior in .NET, which I'd set up in 20 minutes in VB 6. That's certainly not enough of a reason to NOT move to .NET, but it is something to be aware of. The loss of the Image control, and changes to PictureBox, etc ... can really cause some headaches.

Anyway, in closing, I'm going to give RiverGuy a star for being the only one to respond. Also, even though it wasn't the exact answer I was looking for, it got me on the right track. Thanks, dude. :)



 
Thanks for the start. I still can't understand completely what you are doing without seeing the code, but I still think you can get away without using a PictureBox, and especially without using it's .Image and .BackgroundImage properties.
 
Well, maybe I can get away without a pictureBox. But I do need to be able to grab what's there so I can move it. What are you proposing? A hotspot of some kind, or something like that? Somehow, whatever is on the background is going to need to make an event for mouseover and mousedown...
 
You're not suggesting I track the mouse constantly and see if it's over one of my little images all the time, are you? I don't think I'd want to do that... What I'm doing is better than that, IMO.
 
Star, not start. Sorry,

Maybe part of the problem is the .gif's you are using. But I did a test. What I did was make a form (it would be a control for real use). I overrode it's OnPaintBackground. In the form's paint event, I made it draw a .jpg image on the form. I then made it draw a bitmap on top of it. The bitmap was a rectangle. Half of it was blue, and half of it was transparent.

I then drew another bitmap on the form. This bitmap was a red square with a transparent hole through the middle. I made this bitmap follow the mouse around. If the transparent hole was over the transparent part of the rectangular bitmap, then the background image would show through. If it was over the blue part of the rectangle, then the blue would show through the hole. None of it required multiple copies of images or anything like that.

There was no screen flicker whatsoever.

As far as a mouse down and what not, you could EASILY implement this in a user control, and give the user the ability to move not only the control itself around on the form, but move the "top-level", or my donut-hole image around as well.

I don't know what you mean about constantly tracking the mouse. What I would suggest is hitting the MouseMove event. If the left button is down, then you check if its over an image. If it is, then you move that image. And when I say move the image, what I really mean is you reset some module-level points, or some points in a list. You then invalidate the form, which causes it to repaint everything, using your new points as a location. Then, if its not over a little image, then you move the whole control.
 
Ok, I'll take your suggestions under advisement. I guess the real issue is that I never did any real GDI programming in VB 6, so I'm wanting to avoid this stuff as much as possible. I'm not real familiar with it, and I was hoping to stick with what I know as much as possible during this upgrade. It could very well be that the "right way" to do this involves scrapping everything I know and learning graphics programming in-depth. My real background is in databases and real-time systems and stuff like that... For this, .NET is working out great and I love it. For the other ... well, it doesn't hurt to learn new things, I guess. I do understand what you're suggesting now.
 
rmusgrove said:
I guess the real issue is that I never did any real GDI programming in VB 6

Me either. But it's really easy, and doesn't require very much code. You can pick up on most of it in a day. Give it a try sometime, and good luck.

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top