Graphics overlap avoiding transparence

leorob88

Well-known member
Joined
Dec 31, 2020
Messages
56
Programming Experience
1-3
Hi, I have a problem drawing in a PictureBox.
To summarize and make an example: imagine you have to draw a rectangle at 0, 0 and it's 100x100 pixels; then you have to draw another rectangle 10x10 at the center (which should be at 45, 45 if i'm not mistaken) but the result shall be simply the first rectangle...meaning they both get drawn, you just can't distinguish them (or maybe you can find a way to say "if some of the second shape overlaps the first, don't draw the overlapping part).
Basically, i have to draw in the picturebox with a brush and first I create a region to specify where it shall be drawn and where not.
I have a system in a dictionary to pick up coordinates and dimensions of various rectangles. First of all, in the paint event of the picturebox, i create a graphicspath. Then what i do cyclically is:

- go and pick data for the rectangle, to create it
- i add the rectangle to the graphicspath

Then with the graphicspath created and made with every single rectangle, i state that picturebox.region = new region(graphicspath). It DOES work generally speaking.
MAIN PROBLEM IS: when i get to add and draw rectangles that overlap, in the overlap area they just don't get drawn (but the remaining shape of these rectangles does remain drawn), concretely speaking that area becomes "transparent" (as if not drawn).
I don't understand if this problem goes for the graphicpath or for the region and how to avoid it. Someone adviced me to read about Region class methods but the only methods that seemed to fit (union, intersect and complement) worsen the problem. If i cyclically create a graphicspath, with the given rectangle, and then go for region.complement(graphicspath) (and remember, in this second hypothetic case this is a cycle, so next step will be to recreate a graphicspath to complement in the region), at the end what i get is the picturebox gets for region its full space/dimensions.
I spent hours to search and try solutions, so i really hope you can give me some help or concrete direction.

Edit:
I added an image. Basically the second pic you see (the pink lines) should blink above the map on the left, as you see in the 3rd pic. It blinks, that's just fine. Problem is, for now, it doesn't blink like that, instead (following suggestions here below) it blinks pic1/pic2 and repeat. At the beginning instead, when i got transparent overlaps, it blinked exactly like in the pic3, but yeah, overlapped region became transparent as well.
The way this blink is activated is through a Timer, whenever it is activated (interval 500) it loops back and forth. What i forgot to mention and this may be crucial, is that the blink region is another picture box aside that map. In fact, one picturebox contains the image map, the other picturebox is located over the first and what the Timer does cyclically is:

if (evidenziapunti.visible == true) { evidenziapunti.visible = false; } else { evidenziapunti.visible = true; }
 

Attachments

  • Immagine.png
    Immagine.png
    11.5 KB · Views: 28
Last edited:
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region.
It is probably better to use Graphics.Clip property if you want to include/exclude areas of drawing.
 
I read as suggested by you, John, unluckily this cannot work for me, not in this basic way. Further details: i set an image in the picturebox, and what i want to do is draw on this image in specific areas. What i get with this advice is that id DOES work for avoiding overlaps. BUT while this areas are drawn (correctly) the picturebox image gets overwritten (that's why i needed to create a region, to draw in specific areas leaving the other image in background visible). so the areas that don't get drawn (they're not in the region i want) become simply "default color" and cover the image. i'll try to see if i can fix this some way, but still thank you again.
 
That only happens if you use Control.Region, because the control will not paint anything outside that region.
It will not happen with Graphics.Clip, that only affect the part you can draw to.
Maybe you can post a basic code example that shows the problem.
 
This is the paint event of the picturebox after i followed your advice and only draws the image OR the rectangles. The graphicspath this way has no use ( i left it for now just for the sake of being there). Before the change, the 2 rows signed with //THIS were not there, instead there was map.AddRectangle(punto); and after the map.close there was evidenziapunti.region = new Region(map); (evidenziapunti is the picturebox) and i got the overlapsed rectangles as i explained.


GraphicsPath map = new GraphicsPath();
for (int i = 0; i < elencoluoghi.Items.Count; i++)
{
if (evidenzia.ContainsKey(elencoluoghi.Items.ToString()))
{
var param = evidenzia[elencoluoghi.Items.ToString()][risultatigiochi.Text];
Rectangle punto = new Rectangle(new Point(param["x"], param["y"]), new Size(param["larg"], param["alt"]));
e.Graphics.Clip = new Region(punto); //THIS
e.Graphics.FillRegion(new SolidBrush(Color.FromArgb(255, 72, 132)), e.Graphics.Clip); //THIS
}
}
map.CloseFigure();
SolidBrush colora = new SolidBrush(Color.FromArgb(255, 72, 132));
e.Graphics.FillRectangle(colora, 0, 0, 478, 340);
 
So in a loop you set Clip and Fill that clip (that's pointless because drawing doesn't exceed the clip anyway), then you want to fill a large rectangle? The last Clip still applies so you probably want to change that.
If you want to exclude the previous clips you can do that with Region class, it has methods like Exclude, Intersect and Union.
You can also rearrange the code to fill a large rectangle first and then loop and fill smaller parts on top of that.
Mind you, the whole point of clipping is drawing something that is or could exceed the clip and still be limited to the clip.
 
I added an image at the beginning of my thread. I repeat it also here: basically the second pic you see (the pink lines) should blink above the map on the left, as you see in the 3rd pic. It blinks, that's just fine. Problem is, for now, it doesn't blink like that, instead it blinks pic1/pic2 and repeat. At the beginning instead, when i got transparent overlaps, it blinked exactly like in the pic3, but yeah, overlapped region became transparent as well.

I tried removing those last 2 lines (of course, didn't think about that), but still nothing changes (for reasons, the result is identical).
I know region has those methods but as i know/understand (tell me if i'm wrong), region has nothing to do with graphics.fill: in fact, the reason why i use region is basically because i need to draw something over a picture leaving transparent all the rest, because i knew if i used graphics it would have just turned the rest default color without transparence.
Basically i also tried methods, instead of those 2 marked rows above (the ones suggested by you), i didn't use the graphicspath and instead i used cyclically:

evidenziapunti.region.complement(punto); // also i tried methods union and intersect

what i got was the region was the whole picturebox for reasons i don't know. the main structure for defining the region to draw is always this, i just try to do minimal changes with those specific rows that define what to do (map.addrectangle, region.complement, graphics.fill, and such). The idea in fact is simple, you have a pictrure and you just have to draw multiple rectangles on it leaving transparent the rest of the picture, and the rectangles are checked and added cyclically to the region during the paint event. It should work and be very simple as i see it...
 

Attachments

  • Immagine.png
    Immagine.png
    11.5 KB · Views: 25
Last edited:
I recommend you post an example that we can run to see where the problem is. All that is needed is a picturebox with an image and the code to draw one blinking rectangle like you do it.
 
I'm so sorry, forgive me, i added a minor part of a few lines above the image, go and read please, btw it's really simple!
 
I made this another way, so basically I'm posting here the code hoping it can help. What I wanted to achieve (and I did) is the highlighted region to "blink" and not get undone by "double drawing" as i mentioned in the post above. What I changed in this, also, is the fact it no longer blink but rather it blends in/out.
So, i created some variables and a method to get the areas to draw, based on the coordinates I have in a file (a database in json containing xs, ys, widths and heights). Here are the major parts of code needed for this. Some are missing, but they're not needed here, they basically get infos in the database or generate the listboxes content and such, whilst here i'm sharing what i did to make it work the way i wanted.

C#:
//inside Form1: Form

Color colorplaces = Color.FromArgb(255, 255, 72, 132)
int opacity = 8;
string blendopacity = "out"; //you can change this into a bool, your choice, i used a string just as my choice but you just need a variable which will be set on 2 different values
bool[,] coordinatesarray = new bool[480, 340]; //this array has the same size as the pictureboxes
 
 
        public void Form1_Load(object sender, EventArgs e) //for easy use i put some code here
        {
            var pos = this.PointToScreen(abovepicturebox.Location);
            pos = mappicturebox.PointToClient(pos);
            abovepicturebox.Parent = mappicturebox;
            abovepicturebox.Location = pos;
            abovepicturebox.BackColor = Color.Transparent;
        }
        private void mapslistbox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (mapslistbox.Items.Count > 0)
            {
                //basically i have 2 listboxes, both read data inside my json database
                //the first listbox reads the available games for the pokemon and lists them
                //when a game is selected in the listbox, the second one lists the available locations within the game
                //what matters for this thread, though, is this:
                createcoordinates();
                mappicturebox.Invalidate();
                mappicturebox.Image = (Image)Properties.Resources.ResourceManager.GetObject(gamesmaps[mapslistbox.Text]);//this takes the selected game in the listbox and within an array i created it checks which resources image is associated to that game, then uses it for the picturebox
                mappicturebox.Update();
                abovepicturebox.Region = null;
                abovepicturebox.Refresh();
            }
        }
        private void locationslistbox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (locationslistbox.Items.Count > 0)
            {
                abovepicturebox.Region = null;
                abovepicturebox.Refresh();
            }
        }
        private void listgames(int currentpkmn) //this is a method i use to create the games list in the listbox, i'm calling it elsewhere, it's not so important to put that here, basically it's just "when i want all this to activate, i call this method"; also, in that instance, timer1 must be enabled to true
        {
            mapslistbox.Items.Clear();
            mappicturebox.Size = new Size(0, 0);
            abovepicturebox.Size = new Size(0, 0);
            locationslistbox.Items.Clear();
            //i cut the code here but basically now i create the games list in the listbox
            //then
            if (mapslistbox.Items.Count > 0)
            {
                mapslistbox.SelectedIndex = 0;
                locationslistbox.Visible = true;
                mappicturebox.Size = new Size(480, 340);
                abovepicturebox.Size = new Size(480, 340);
                //i cut the code here but basically i autodetect possible item in the lists matching the user's request and auto-select it (so, game and location)
            }
            else
            {
                locationslistbox.Visible = false;
            }
        }
        private void createcoordinates()
        {
            for (int x = 0; x < 480; x++)
            {
                for (int y = 0; y < 340; y++)
                {
                    coordinatesarray[x, y] = false;
                }
            }
            for (int i = 0; i < locationslistbox.Items.Count; i++)
            {
                string location = locationslistbox.Items[i].ToString();
                if (/*i'm cheking here IF there are coordinates for that game; THEN*/)
                {
                    var param = database[location][mapslistbox.Text];
                    int x = param["x"], y = param["y"], wid = param["width"], hei = param["height"]; //most locations are related to a rectangle with these 4 parameters
                    for (int x = 0; x < wid / 5; x++)
                    {
                        for (int y = 0; y < hei / 5; y++)
                        {
                            coordinatesrray[x + (x * 5), y + (y * 5)] = true;//so, since later i'm gonna draw the highlights as a bunch of 5x5 pixels rectangles, now in the array i'm stating the points where they'll be drawn, so there's no issue when i state twice or more that a bool shall be true
                        }
                    }
                }
            }
        }
        private void abovepicturebox_Paint(object sender, PaintEventArgs e)
        {
            //i cut the code there but basically with 2 "for" loops (x;y) i'm cheking the first available coordinate to draw on the highlight and store them in 2 variables
            GraphicsPath map = new GraphicsPath();
            for (int x = /*first avail x*/; x < abovepicturebox.Width; x += 5)
            {
                for (int y = /*first avail y*/); y < abovepicturebox.Height; y += 5)
                {
                    if (coordinatesarray[x, y] == true)
                    {
                        map.AddRectangle(new Rectangle(x, y, 5, 5));
                    }
                }
            }
            map.CloseFigure();
            abovepicturebox.Region = new Region(map);
            Brush mybrush = new SolidBrush(colorplaces);
            e.Graphics.FillRectangle(mybrush, 0, 0, 480, 340);
        }
        //i'm using a timer for the blend, the lower the interval, the faster the animation
        //whenever i want to hide the map and all, i also set the timer enabled to false
        private void timer1_Tick(object sender, EventArgs e)
        {
            if (blendopacity == "down")
            {
                opacity -= 1;
                if (opacity == 1) { blendopacity = "up"; }

            }
            else
            {
                opacity += 1;
                if (opacity == 8) { blendopacity = "down"; }
            }
            colorplaces = Color.FromArgb(255 / 8 * opacity, 255, 72, 132);
            abovepicturebox.Refresh();
        }
 
Last edited:

Latest posts

Back
Top Bottom