How to avoid overlapping of opaque rectangles on graphics.drawrectangle()? - vb.net

After a bunch of fail on Google searches finally I thought for asking for experts here to help me at this problem as this site always helped me.
What I want?
I want to create a Highlighter for my some kind of drawing application. I want this to be similar to the highlighter you can see on the Windows Snipping Tool.
What is my problem?
The problem is that although I can draw the semitransparent or opaque rectangles using the code, gfx.FillRectangle(New SolidBrush(Color.FromArgb(100, Colors.GreenYellow)), x, y, width, height), but if I draw another rectangle overlapping any previous rectangles the colors gets darker and reduces the transparency of the rectangles where they overlapped.
Code:
Public Class Form1
Dim drag As Boolean
Dim mouseX, mouseY As Integer
Dim prev As Point
Dim initi As Point
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
Dim grx As Graphics = Panel1.CreateGraphics
grx.DrawString("+", New Font("Arial", 144, FontStyle.Regular), New SolidBrush(Color.FromArgb(100, Color.GreenYellow)), New Point(200, 200))
End Sub
Private Sub Panel1_MouseDown(sender As Object, e As MouseEventArgs) Handles Panel1.MouseDown
drag = True
mouseX = MousePosition.X - Me.Left - 8
mouseY = MousePosition.Y - Me.Top - 34
initi = New Point(mouseX, mouseY)
End Sub
Private Sub Panel1_MouseMove(sender As Object, e As MouseEventArgs) Handles Panel1.MouseMove
If drag Then
mouseX = MousePosition.X - Me.Left - 8
mouseY = MousePosition.Y - Me.Top - 34
End If
End Sub
Private Sub Panel1_MouseUp(sender As Object, e As MouseEventArgs) Handles Panel1.MouseUp
drag = False
prev = New Point(0, 0)
Dim grx As Graphics = Panel1.CreateGraphics
grx.FillRectangle(New SolidBrush(Color.FromArgb(100, Color.GreenYellow)), initi.X, initi.Y, (mouseX - initi.X), (mouseY - initi.Y))
End Sub
End Class
Screenshot of the application (Showing the problem)
Left "+" is the one that I want to draw.
Right "+" is the one that I get when I draw.

I tried this and it works: There are no borders you can't even distinguish the different boxes:
I changed the following:
New SolidBrush(Color.FromArgb(100, Color.GreenYellow)), New Point(200, 200))
Into this:
New SolidBrush(Color.GreenYellow), New Point(200, 200))
For BOTH times: For the declarations and for the Panel1_MouseDown
That is because this sets the Alpha to 1 by defualt making unchangable as well, so over lapping will not change any colours, layers, or visibility.
You should only use FromArgb when you are going to control the alpha of the colour, but in this case you are letting the computer do that for you

Well. I've never really used graphics, but the only thing I could come up with is to create a list of the highlights to be drawn and then each time the mouse_up event fires, create a new bitmap, draw each rectangle pixel by pixel, with semi-transparent pixels to the bitmap and then draw the resulting bitmap to the panel using the panel's paint event handler that fires when you refresh the panel. This seems the built-in alpha blending that .net does automatically.
Add this to your form's variable declarations
Dim highlightsList As New List(Of Rectangle)
Dim bmp1 As Bitmap
alter your form_shown event to
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
bmp1 = New Bitmap(Panel1.Width, Panel1.Height)
End Sub
add this sub which handles the addition of the rectangles to the list and the creation of the bitmap
Private Sub addRectangle(gr As Graphics, x As Integer, y As Integer, v1 As Integer, v2 As Integer)
Dim newRectangle As New Rectangle(x, y, v1, v2)
highlightsList.Add(newRectangle)
Using G As Graphics = Graphics.FromImage(bmp1)
G.Clear(Color.White)
End Using
bmp1.MakeTransparent(Color.White)
For Each rect As Rectangle In highlightsList
For i As Integer = rect.X To rect.X + rect.Width - 1
For j As Integer = rect.Y To rect.Y + rect.Height - 1
bmp1.SetPixel(i, j, Color.FromArgb(100, Color.GreenYellow))
Next
Next
Next
Panel1.Refresh()
End Sub
add a handler for the panel's paint event so that when you refresh the panel, the bitmap is drawn onto it
Private Sub Panel1_Paint(sender As Object, e As PaintEventArgs) Handles Panel1.Paint
e.Graphics.DrawImage(bmp1, 0, 0)
End Sub
and change your mouse_up event to use the above sub to do the drawing
Private Sub Panel1_MouseUp(sender As Object, e As MouseEventArgs) Handles Panel1.MouseUp
drag = False
prev = New Point(0, 0)
Dim grx As Graphics = Panel1.CreateGraphics
'grx.Clear(Panel1.BackColor)
addRectangle(grx, initi.X, initi.Y, (mouseX - initi.X), (mouseY - initi.Y))
End Sub
This seems to work, but if you're using the panel for anything other that showing highlights, it may not work as expected.

Related

How to draw a right triangle in vb.net WinForm?

I have x ImageButtons in my WinForm each ImageButton has the same height and width and works like a radio-button group, the user can press only one ImageButtton at the time.
I would add a triangle at the center of each ImageButton when it's clicked like this:
I was trying to draw a Triangle by using Graphics but i can't get how to make thar triangle pointing at the Right and anchored to the center of the image button...
Here is the code i've tried to draw the triangle
With e.Graphics
Using gp As New GraphicsPath
gp.AddLine(150, 20, 180, 240)
gp.AddLine(180, 240, 20, 240)
gp.CloseFigure()
.FillPath(Brushes.Red, gp)
.DrawPath(Pens.Red, gp)
End Using
End With
In your shoes I’ll pay attention about comments above as if you do that everything will be easier in a long term prospective.
Saying that in a short term this code gives an idea to start with:
Private Sub Button2_Paint(sender As Object, e As PaintEventArgs) Handles Button2.Paint
Dim w As Single = 30
Dim h As Single = 50
Dim centerY As Single = CSng(Button2.Height / 2)
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
e.Graphics.CompositingQuality = CompositingQuality.HighQuality
Using gp As New GraphicsPath()
gp.AddLine(0, centerY - h / 2, w, centerY)
gp.AddLine(w, centerY, 0, centerY + h / 2)
e.Graphics.DrawPath(Pens.Red, gp)
e.Graphics.FillPath(Brushes.Red, gp)
End Using
End Sub
And let me add something else which is: your triangle (as your image shows) is a Left triangle not a Right triangle.
Se hai necessità di farlo al click e riportare allo stato iniziale tutti gli altri parti da qui:
Private Event UpdateBtns(caller As Button)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RaiseEvent UpdateBtns(Button1)
End Sub
Private Sub Button2_Paint(sender As Object, e As PaintEventArgs) Handles Button2.Paint
AddHandler UpdateBtns, Sub(caller As Button)
If (caller.Name <> CType(sender, Button).Name) Then CType(sender, Button).Refresh()
End Sub
If ActiveControl Is CType(sender, Button) Then
Dim w As Single = 30
Dim h As Single = 50
Dim centerY As Single = CSng(Button2.Height / 2)
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
e.Graphics.CompositingQuality = CompositingQuality.HighQuality
Using gp As New GraphicsPath()
gp.AddLine(0, centerY - h / 2, w, centerY)
gp.AddLine(w, centerY, 0, centerY + h / 2)
e.Graphics.DrawPath(Pens.Red, gp)
e.Graphics.FillPath(Brushes.Red, gp)
End Using
End If
End Sub

Center form on screen during runtime

I'm using vb.net, and what I have in mind is on clicking a button, the current form will expand and it will be centered on screen.
I've figured out how to expand the form, but I can't center it on screen.
I've tried using this code but it's not what I wanted to get.
Dim Width As Integer = Screen.PrimaryScreen.Bounds.Width
Dim Height As Integer = Screen.PrimaryScreen.Bounds.Height
Me.Location = New Point(Height / 2, Width / 2)
or otherwise: Me.Location = New Point(Width / 2, Height/ 2)
even tried this: Me.StartPosition = FormStartPosition.Center
Use .CentreToScreen()
Like this:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Me.Size = New System.Drawing.Size(400, 650)
Me.CenterToScreen()
End Sub

How to prevent graphics from resetting [duplicate]

I need to put some graphics in one section of a TableLayoutPanel.
I'm doing this by creating a PictureBox in one cell of the TLP.
Can't get two things to work:
1) The initial display is blank! Drawing appears only when you resize the form
2) The resize event doesn't fire equally when expanding the size as compared contracting.
Any suggestions to improve the above two problems would be great!
Here is my code. The form has a 2x2 TableLayoutPanel in it, and one cell of the TLP has a PictureBox in it. Both the TLP and the PictureBox are set to Fill Parent:
Imports System.Drawing.Drawing2D
Public Class Form1
Private g As Graphics
Dim n As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Debug.Print(String.Format("{0}{0}Form1_Load at {1}", vbCrLf, Now()))
Me.SetDesktopLocation(800, 200)
End Sub
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
n += 1
Debug.Print(String.Format("MyBase.Paint: {0}", n))
DisplayMyStuff()
End Sub
Private Sub PictureBox1_Resize(sender As Object, e As EventArgs) Handles Pict ureBox1.Resize
n += 1
Debug.Print(String.Format("PictureBox1.Resize: {0} PictureBoxSize = {1} / {2}", n, PictureBox1.Width, PictureBox1.Height))
If g IsNot Nothing Then
g.Dispose()
End If
g = PictureBox1.CreateGraphics()
End Sub
Private Sub DisplayMyStuff()
Dim rect1 As Rectangle
Dim rect2 As Rectangle
Dim pt1 As New Point(50, 50)
Dim pt2 As New Point(100, 100)
Dim pt3 As New Point(150, 150)
Dim brR As New SolidBrush(Color.Red)
Dim linGradBr As New LinearGradientBrush(pt2, pt3, Color.Yellow, Color.Blue)
Dim pictBoxSize As Size
Dim sz As Size
Dim width, height As Integer
pictBoxSize = New Size(CType(PictureBox1.Size, Point))
width = CInt(pictBoxSize.Width / 2)
height = CInt(pictBoxSize.Height / 2)
sz = New Size(width, height)
n += 1
Debug.Print(String.Format("DisplayMyStuff: {0}, Half-Size = {1} / {2}", n, width, height))
g.Clear(Color.Bisque)
rect1 = New Rectangle(pt1, sz)
rect2 = New Rectangle(pt2, sz)
g.FillRectangle(brR, rect1)
g.FillRectangle(linGradBr, rect2)
brR.Dispose()
linGradBr.Dispose()
End Sub
End Class
Apparently, you are trying to draw to a picturebox (g = PictureBox1.CreateGraphics())
The reason stuff disappears is that when the size changes, or something passes over the window, the controls and form need to be repainted. This happens in the Paint event, so your code needs to do the drawing there. Unlike a PictureBox image, items drawn to a form or control are not persistent on their own, that is done by drawing in the Paint event.
This is essentially your DrawMyStuff procedure relocated to the Picbox's Paint event.
Private Sub PictureBox1_Paint(sender As Object,
e As PaintEventArgs) Handles PictureBox1.Paint
Dim pt1 As New Point(50, 50)
Dim pt2 As New Point(100, 100)
Dim pt3 As New Point(150, 150)
Dim sz As New Size(CInt(PictureBox1.Size.Width / 2),
CInt(PictureBox1.Size.Height / 2))
n += 1
Debug.Print(String.Format("DisplayMyStuff: {0},
Half-Size = {1} / {2}", n, sz.Width, sz.Height))
Dim rect1 As New Rectangle(New Point(50, 50), sz)
Dim rect2 As New Rectangle(New Point(100, 100), sz)
Using linGradBr As New LinearGradientBrush(pt2, pt3, Color.Yellow, Color.Blue)
e.Graphics.Clear(Color.Bisque)
e.Graphics.DrawRectangle(Pens.Black, rect1)
e.Graphics.DrawRectangle(Pens.Black, rect2)
e.Graphics.FillRectangle(Brushes.Red, rect1)
e.Graphics.FillRectangle(linGradBr, rect2)
End Using
End Sub
If you are actually trying to paint on the Form, then Grim's answer is the solution. There you respond to the Form Paint event. In either case, use the Graphics object provided by Windows as an EventArg.
Above, you are using the Graphics object for the PictureBox (via event args) so output is to the PictureBox.
Windows wont know you are drawing something in the Paint event, so you need to tell it that the image needs to be updated at certain times such as when the PictureBox is resized. In the resize event, add:
PictureBox1.Invalidate ' tell windows it needs to be redrawn
' or
PictureBox1.Refresh ' redraw now
Me.Refresh is a bit of overkill because the entire form likely does not need to be repainted.
As Hans Passant says. First get rid of;
Private g As Graphics
and the whole of the PictureBox1_Resize(...)... routine. Then change the following routines to be like so;
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
DisplayMyStuff(e.Graphics)
End Sub
and
Private Sub DisplayMyStuff(pGraphics As Graphics)
Dim pt1 As New Point(50, 50)
Dim pt2 As New Point(100, 100)
Dim pt3 As New Point(150, 150)
Dim pictBoxSize As New Size(CType(PictureBox1.Size, Point))
Dim width As Integer = CInt(pictBoxSize.Width / 2)
Dim height As Integer = CInt(pictBoxSize.Height / 2)
Dim sz As New Size(width, height)
pGraphics.Clear(Color.Bisque)
Dim rect1 As New Rectangle(pt1, sz)
Dim rect2 As New Rectangle(pt2, sz)
Using linGradBr As New LinearGradientBrush(pt2, pt3, Color.Yellow, Color.Blue)
pGraphics.FillRectangle(Brushes.Red, rect1)
pGraphics.FillRectangle(linGradBr, rect2)
End Using
End Sub
.. then test. Please report back to tell me that you've learned something!! Especially.. that you don't need to create a new red brush - all 'standard' colours are built in - and that using the graphics object properly leads to better, smoother displays.

Having trouble drawing simple rectangle in picturebox

I have a picture box that will be showing an image. I want the user to be able to click, drag, and mouse up to a rectangle on the image. Like "I want to do something using this rectangle I drew here on this picture". If they click again, I want the previous rectangle to disappear and they start over or when I click a button to clear the highlighting rectangle they drew.
So I did find some good starting code from the msdn example about creating a zoom in rubber band rectangle that I edited a bit below, but I'm having some issues with:
Public bHaveMouse As Boolean
Public ptOriginal As Point
Public ptLast As Point
Public rect As Rectangle
Public b_Redraw As Boolean = False
' and Normalize the points and draw the reversible frame.
Private Sub MyDrawReversibleRectangle(ByVal p1 As Point, ByVal p2 As Point)
Try
'clear
' Convert the points to screen coordinates.
p1 = PointToScreen(p1)
p2 = PointToScreen(p2)
' Normalize the rectangle.
If (p1.X < p2.X) Then
rect.X = p1.X
rect.Width = p2.X - p1.X
Else
rect.X = p2.X
rect.Width = p1.X - p2.X
End If
If (p1.Y < p2.Y) Then
rect.Y = p1.Y
rect.Height = p2.Y - p1.Y
Else
rect.Y = p2.Y
rect.Height = p1.Y - p2.Y
End If
If rect.Width > pbZoneImage.Width Then
rect.Width = pbZoneImage.Width
End If
If rect.Height > pbZoneImage.Height Then
rect.Height = pbZoneImage.Height
End If
' Draw the reversible frame.
ControlPaint.DrawReversibleFrame(rect, Color.Red, FrameStyle.Thick)
Catch ex As Exception
End Try
End Sub
Private Sub pbZoneImage_MouseDown(sender As Object, e As MouseEventArgs) Handles pbZoneImage.MouseDown
If e.Button <> Windows.Forms.MouseButtons.Left Then
Exit Sub
End If
Try
' Make a note that we "have the mouse".
bHaveMouse = True
' Store the "starting point" for this rubber-band rectangle.
If b_Redraw Then
If (ptLast.X <> -1) Then
' Dim ptCurrent As Point
'ptCurrent.X = e.X
'ptCurrent.Y = e.Y
MyDrawReversibleRectangle(ptOriginal, ptLast)
End If
' Set flags to know that there is no "previous" line to reverse.
ptLast.X = -1
ptLast.Y = -1
ptOriginal.X = -1
ptOriginal.Y = -1
End If
ptOriginal.X = e.X
ptOriginal.Y = e.Y
' Special value lets us know that no previous
' rectangle needs to be erased.
ptLast.X = -1
ptLast.Y = -1
Catch ex As Exception
End Try
End Sub
Private Sub pbZoneImage_MouseMove(sender As Object, e As MouseEventArgs) Handles pbZoneImage.MouseMove
Dim ptCurrent As Point
ptCurrent.X = e.X
ptCurrent.Y = e.Y
' If we "have the mouse", then we draw our lines.
If (bHaveMouse) Then
' If we have drawn previously, draw again in
' that spot to remove the lines.
If (ptLast.X <> -1) Then
MyDrawReversibleRectangle(ptOriginal, ptLast)
End If
' Update last point.
ptLast = ptCurrent
' Draw new lines.
MyDrawReversibleRectangle(ptOriginal, ptCurrent)
End If
End Sub
Private Sub pbZoneImage_MouseUp(sender As Object, e As MouseEventArgs) Handles pbZoneImage.MouseUp
'Try
' ' Set internal flag to know we no longer "have the mouse".
bHaveMouse = False
End Sub
My Problem: Sometimes when drawing it doesn't remove the previously drawn rectangle, or if I hover over certain buttons (like the exit button) the rectangles disappear! I want they to stay so that I can record the start and end point of the rectangle for other programs. I want them to disappear when I hit my clear rectangle button, but I feel like I am getting confused on something that should be very simple.
Another issue is I'm trying to keep the rectangle from spilling outside the picture box (Pbzoneimage). But it does, and changes color.
Where did I go wrong? Is there is a better way to draw this altogether?
You need two bitmaps, one for the picturebox (img) and one to clear it and draw the rectangle(imgClone).
Private mouse_Down As Point
Private img As Bitmap
Private imgClone As Bitmap
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
img = My.Resources..... 'or you can load the image from file
PictureBox1.Image = img 'with this every time you invalidate it draws img to picturebox
imgClone = CType(PictureBox1.Image.Clone, Bitmap)
End Sub
Private Sub PictureBox1_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
mouse_Down = e.Location
End Sub
Private Sub PictureBox1_MouseMove(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
If e.Button = MouseButtons.Left And e.Location <> mouse_Down Then
DrawRectangle(e.Location)
End If
End Sub
Private Sub DrawRectangle(ByVal pnt As Point)
Dim g As Graphics
g = Graphics.FromImage(img)
g.DrawImage(imgClone, 0, 0) 'we are clearing img with imgClone. imgClone contains the original image without the rectangles
If pnt.X = mouse_Down.X Or pnt.Y = mouse_Down.Y Then
g.DrawLine(Pens.Firebrick, mouse_Down.X, mouse_Down.Y, pnt.X, pnt.Y)
Else
g.DrawRectangle(Pens.Firebrick, Math.Min(mouse_Down.X, pnt.X), Math.Min(mouse_Down.Y, pnt.Y),
Math.Abs(mouse_Down.X - pnt.X), Math.Abs(mouse_Down.Y - pnt.Y))
End If
g.Dispose()
PictureBox1.Invalidate() 'draw img to picturebox
End Sub
If you need to clear the picturebox:
Dim g As Graphics
g = Graphics.FromImage(img)
g.DrawImage(imgClone, 0, 0)
g.Dispose()
PictureBox1.Invalidate()
valter

How to screenshot only the area inside an Ellipse?

I'm creating a little snipping tool-like program in VB.NET and I can screenshot any area I want provided it is a rectangle area. I select the area in the screen and save it as an image. That's easy.
My problem is that I want to be able to screenshot not only a rectangular (standard rectangle shape area), but selecting/drawing an ellipse and screenshot the inside part of it. See the image below:
is there any way to achieve this or any library I can use for that?
Here's my current code:
Public Class Form3
Private _bRubberBandingOn As Boolean = False
Private _pClickStart As New Point
Private _pClickStop As New Point
Private _pNow As New Point
Private Sub Form3_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
Me._bRubberBandingOn = Not _bRubberBandingOn
If Me._bRubberBandingOn Then
If _pClickStart = Nothing Then _pClickStart = New Point
_pClickStart.X = e.X
_pClickStart.Y = e.Y
_pNow.X = e.X
_pNow.Y = e.Y
End If
Me.Invalidate()
End Sub
Private Sub Form3_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
If Me._bRubberBandingOn Then
If _pNow = Nothing Then _pNow = New Point
Me._pNow.X = e.X
Me._pNow.Y = e.Y
Me.Invalidate()
End If
End Sub
Private Sub Form3_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
Me._bRubberBandingOn = Not Me._bRubberBandingOn
If Not Me._bRubberBandingOn Then
If _pClickStop = Nothing Then _pClickStop = New Point
_pClickStop.X = e.X
_pClickStop.Y = e.Y
Me.Invalidate()
End If
End Sub
Private Sub Form3_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim _rRectangle As New Rectangle
Dim _penNew As New Pen(Color.Black, 2)
_rRectangle.X = _pClickStart.X
_rRectangle.Y = _pClickStart.Y
If Me._bRubberBandingOn Then
_rRectangle.Width = Me._pNow.X - _pClickStart.X
_rRectangle.Height = Me._pNow.Y - _pClickStart.Y
Else
_rRectangle.Width = Me._pClickStop.X - _pClickStart.X
_rRectangle.Height = Me._pClickStop.Y - _pClickStart.Y
End If
_penNew.DashStyle = Drawing2D.DashStyle.Solid
e.Graphics.DrawEllipse(_penNew, _rRectangle)
End Sub
End Class
Is there any way to achieve this or any library I can use for that?
Is it any way to get a handle of that painted line/shape and then use it to create a screenshot? I actually searched about this but didn't found anything meaningful yet.
thanks in advance for your time.
Take the image that you are drawing the ellipse on top of and do the following:
Dim theBitmap As Bitmap = DirectCast(Image.FromFile("PathToFileYouAreDrawingEllipseOn.bmp"), Bitmap)
Dim theEllipseBitmap As New Bitmap(theBitmap.Width, theBitmap.Height)
Dim theGraphics As Graphics = Graphics.FromImage(theEllipseBitmap)
Dim theGraphicsPath As New GraphicsPath()
' The (10,10) coordinates here are made up, you will need to take what is drawn by the user (starting x,y; ending x,y, etc.)
theGraphicsPath.AddEllipse(10, 10, theBitmap.Width - 20, theBitmap.Height - 20)
theGraphics.Clear(Color.Magenta)
theGraphics.SetClip(theGraphicsPath)
theGraphics.DrawImage(theBitmap, New Rectangle(0, 0, theBitmap.Width, theBitmap.Height), 0, 0, theBitmap.Width, theBitmap.Height, _
GraphicsUnit.Pixel)
theGraphics.Dispose()
theEllipseBitmap.MakeTransparent(Color.Magenta)
' Save the ellipse bitmap to a PNG file format
string fileName = "PathToYourDesiredOutput.png"
theEllipseBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png)