Draw centered in form on moving image - vb.net

I'm trying to implement a small tool that draws sin and cos functions. The program is supposed to draw from the center of the form, so that the history will extend to the right. Imagine the following gif but with the right end of the line moving up and down, and the path to the left "showing the trace"
What I would like to do is, every time a timer elapses, draw a point (via Graphics.FillRectangle) in the center of a PictureBox. In the next timer fire move the graphics one pixel to the left, and draw the next pixel. This is what I have so far:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
bmp = New Drawing.Bitmap(PictureBox1.Size.Width, PictureBox1.Size.Height)
g1 = Graphics.FromImage(bmp)
MathTimer = New Timers.Timer(30)
AddHandler MathTimer.Elapsed, AddressOf OnTimedEvent
MathTimer.Enabled = True
MathTimer.Start()
End Sub
Private Sub OnTimedEvent(source As Object, e As System.Timers.ElapsedEventArgs)
g1.FillRectangle(Brushes.Red, PictureBox1.Size.Width \ 2, PictureBox1.Size.Height \ 2, 1, 1)
g1.TranslateTransform(-1, 0)
PictureBox1.Image = bmp
End Sub
However, this doesn't achieve the desired effect, since the canvas of the graphics object g1 is moved to the left with this. Eventually it's not drawing anymore. (No wonder, since with this I'm drawing "with the left end of the line")
Anybody have a better idea that achieves the desired effect?

For i As Integer = 0 To pointsToDraw.Count - 2
Dim p As Point = pointsToDraw(i)
Dim xPos As Integer = (pctrBxSinCosDraw.Width / 2) + p.X - currentTick
e.Graphics.FillRectangle(Brushes.Black, xPos, CInt(p.Y + pctrBxSinCosDraw.Height / 2), 1, 1)
If xPos <= 0 Then
pointsToDraw.RemoveAt(i)
End If
Next
Where currentTick is set by a Timer, which on Tick, calculates the x/y values:
Dim yVal As Double
If useSinCalc Then
yVal = Math.Sin(DegreeToRadian(currentTick)) * (180 / Math.PI)
Else
yVal = Math.Cos(DegreeToRadian(currentTick)) * (180 / Math.PI)
End If
pointsToDraw.Add(New Point(currentTick, yVal))
currentTick += 1
pctrBxSinCosDraw.Invalidate()
And DegreeToRadian simply does (as it states):
Private Function DegreeToRadian(ByVal angle As Double)
DegreeToRadian = Math.PI * angle / 180.0
End Function
And pointsToDraw is List(Of Point)
A sample project can be found on my download page.

Related

Giving Dynamically Created Shapes a Name

I'm designing a hexagon grid and I need to be able to name each hexagon, so I can refer to them later. Below is my class, it generates the hexagon grid, and I've labeled the code throughout so you can understand what's happening.
I've been searching for a while now reading a lot about Graphics, but I can't get a working design with the answers I've seen offered. Perhaps, I'm going about this wrong by using Graphics, but my plan is to be able to click on each hexagon and do something with it.
Note: If you see a way to improve my code let me know. It's appreciated!
' Generate Hexagon Grid
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
' Hexagon Grid Parameters
Dim HexagonRadius As Integer = 20 ' Fix "Position Hexagon Grid Columns" Before Changing Hexagon Radius
Dim GridSize As Integer = 10
' Generate Hexagon Grid
Dim HexagonX As Integer = HexagonRadius
Dim HexagonY As Integer = HexagonRadius
For i As Integer = 1 To GridSize
For j As Integer = 1 To GridSize
' Hexagon Vertex Coordinates
Dim point1 As New Point((HexagonX - HexagonRadius), (HexagonY))
Dim point2 As New Point((HexagonX - (HexagonRadius / 2)), (HexagonY + ((HexagonRadius / 2) * Math.Sqrt(3))))
Dim point3 As New Point((HexagonX + (HexagonRadius / 2)), (HexagonY + ((HexagonRadius / 2) * Math.Sqrt(3))))
Dim point4 As New Point((HexagonX + HexagonRadius), (HexagonY))
Dim point5 As New Point((HexagonX + (HexagonRadius / 2)), (HexagonY - ((HexagonRadius / 2) * Math.Sqrt(3))))
Dim point6 As New Point((HexagonX - (HexagonRadius / 2)), (HexagonY - ((HexagonRadius / 2) * Math.Sqrt(3))))
Dim hexagonPoints As Point() = {point1, point2, point3, point4, point5, point6}
' Create Hexagon
e.Graphics.FillPolygon(Brushes.Green, hexagonPoints)
' Hexagon Outline
e.Graphics.DrawLine(Pens.Black, point1, point2)
e.Graphics.DrawLine(Pens.Black, point2, point3)
e.Graphics.DrawLine(Pens.Black, point3, point4)
e.Graphics.DrawLine(Pens.Black, point4, point5)
e.Graphics.DrawLine(Pens.Black, point5, point6)
e.Graphics.DrawLine(Pens.Black, point6, point1)
' Position Hexagon Grid Columns
HexagonY += 34 ' Specific to Hexagon Radius: 20
Next
If i Mod 2 > 0 Then
HexagonY = 36.75 ' Specific to Hexagon Radius: 20
Else
HexagonY = 20 ' Specific to Hexagon Radius: 20
End If
HexagonX += 30 ' Specific to Hexagon Radius: 20
Next
End Sub
You'll need to create some Hexagon class with it's coordinates and (maybe name, if really needed). And save them to some suitable collection (2-dimensional array maybe?)
This should happen somewhere outside your Paint event and might be recalculated on grid SizeChanged event.
Inside your Paint event you'll just iterate throught existing collection and render according to pre-computed coordinates.
OnClick event will loop throught the same collection to find specific Hexagon for updating (changing background color for example) and forcing form to repaint to take effect.
For large rendering you should consider rendering to bitmap first and drawing that final bitmap to e.Graphics for faster work. Your bitmap could be cached as well to speed up even more.
EDIT: Code sample added
Turn Option Strict On in your project properties to avoid many problems in your code that you're not aware of.
Public Class frmTest
Private Const HexagonRadius As Integer = 20
Private Const GridSize As Integer = 10
Private fHexagons As New List(Of Hexagon)
Private fCache As Bitmap
Private fGraphics As Graphics
Private Sub ResetHexagons() 'Call when some parameter changes (Radius/GridSize)
fHexagons.Clear()
Invalidate()
End Sub
Private Function EnsureHexagons() As List(Of Hexagon)
Dim X, Y As Single, xi, yi As Integer
If fHexagons.Count = 0 Then
X = HexagonRadius : Y = HexagonRadius
For xi = 1 To GridSize
For yi = 1 To GridSize
fHexagons.Add(New Hexagon(HexagonRadius, X, Y))
Y += 34
Next
'Do your math to get theese values from HexagonRadius value
If xi Mod 2 > 0 Then
Y = 36.75
Else
Y = 20
End If
X += 30
Next
fCache?.Dispose()
fGraphics?.Dispose()
fCache = New Bitmap(GridSize * HexagonRadius * 2, GridSize * HexagonRadius * 2)
fGraphics = Graphics.FromImage(fCache)
For Each H As Hexagon In fHexagons
H.Render(fGraphics)
Next
End If
Return fHexagons
End Function
Private Sub frmTest_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
EnsureHexagons()
e.Graphics.DrawImageUnscaled(fCache, Point.Empty)
End Sub
Private Sub frmTest_MouseClick(sender As Object, e As MouseEventArgs) Handles Me.MouseClick
Dim H As Hexagon = EnsureHexagons.FirstOrDefault(Function(X) X.Contains(e.Location))
If H IsNot Nothing Then
H.Checked = Not H.Checked
H.Render(fGraphics) 'Update cache without repainting all
Invalidate()
End If
End Sub
End Class
Public Class Hexagon
Public ReadOnly Radius, X, Y As Single
Public ReadOnly Points() As PointF
Public Property Checked As Boolean
Public Sub New(Radius As Single, X As Single, Y As Single)
Me.Radius = Radius : Me.X = X : Me.Y = Y
Points = {New PointF((X - Radius), (Y)),
New PointF((X - (Radius / 2)), CSng(Y + ((Radius / 2) * Math.Sqrt(3)))),
New PointF((X + (Radius / 2)), CSng(Y + ((Radius / 2) * Math.Sqrt(3)))),
New PointF((X + Radius), (Y)),
New PointF((X + (Radius / 2)), CSng(Y - ((Radius / 2) * Math.Sqrt(3)))),
New PointF((X - (Radius / 2)), CSng(Y - ((Radius / 2) * Math.Sqrt(3.0!))))}
End Sub
Public Sub Render(G As Graphics)
' Create Hexagon
G.FillPolygon(If(Checked, Brushes.Blue, Brushes.Green), Points)
' Hexagon Outline
For i As Integer = 0 To Points.Length - 1
G.DrawLine(Pens.Black, Points(i), Points((i + 1) Mod Points.Length))
Next
End Sub
Public Function Contains(P As Point) As Boolean
'Do your math here, this is just simplified estimation
Return X - Radius <= P.X AndAlso P.X <= X + Radius AndAlso Y - Radius <= P.Y AndAlso P.Y <= Y + Radius
End Function
End Class

Drawing rect in picturebox not done to right scale for mouse

I currently have a picture box where the user will click and drag to draw a rectangle over an image (one that can be changed regularly). When they're done (mouse_up), I will display the relative points of the rect in a text box to the resolution.
So, for example, the user draws from top left (0,0) to bottom right of a 1920 x 680 image (picturebox.right, picturebox.bottom) for a rect, the text box will show (1920,680) for the end point. That's mostly just ratio stuff.
I am using the code from an answer of a previous question of mine (Having trouble drawing simple rectangle in picturebox) to draw it.
The Problem: The box doesn't follow the mouse since the images have to be done in stretch mode. They're usually pretty large (like 1920 x 680) and can't fit in a regular gui. There are multiple resolutions, so got to go dynamic with the ratios. Without editing, this code works great in normal mode, but that doesn't work for usability. So, when you draw the box, it's really small and not relative to the mouse (so I can't display the end point on the textboxes).
Here's an example of what I mean. I've dragged my mouse halfway across the image:
What I've tried: I've attempted to counter act it by ratios, but it still doesn't fix the displaying the end point issue, or does it really follow the mouse that well. It's usually off by at least 10 or so pixels to the left. Here's my adjusted code for that:
Private Sub DrawRectangle(ByVal pnt As Point)
Try
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
Dim w_ratio As Integer = Math.Floor(img.Width / pbZoneImage.Width)
Dim h_ratio As Integer = Math.Floor(img.Height / pbZoneImage.Height)
Dim customPen As New Pen(currentColor, 5)
'If pnt.X = mouse_Down.X Or pnt.Y = mouse_Down.Y Then
' g.DrawLine(customPen, mouse_Down.X, mouse_Down.Y, pnt.X * w_ratio, pnt.Y * h_ratio)
'Else
theRectangle = New Rectangle(Math.Min(mouse_Down.X, pnt.X * w_ratio), Math.Min(mouse_Down.Y, pnt.Y * h_ratio),
Math.Abs(mouse_Down.X - pnt.X * w_ratio), Math.Abs(mouse_Down.Y - pnt.Y * h_ratio))
g.DrawRectangle(customPen, theRectangle)
'End If
g.Dispose()
pbZoneImage.Invalidate() 'draw img to picturebox
Catch ex As Exception
End Try
End Sub
I've also tried just getting the end display point (x,y) to match the relative end of the rectangle, but again it isn't working with the ratios.
Any ideas on how to make this work as well as it does in normal mode as it does in stretch? I'm also open to different controls or just any tips in general. Thanks!
This can be done with many ways but the easiest is to use a picturebox with SizeMode = Normal. Load your images:
img = New Bitmap(pbZoneImage.Width, pbZoneImage.Height)
imgClone = My.Resources.... 'real dimensions
Dim g As Graphics = Graphics.FromImage(img)
'it will scale the image, no need for stretch mode
g.DrawImage(imgClone, 0, 0, pbZoneImage.Width, pbZoneImage.Height)
g.Dispose()
pbZoneImage.Image = img
Then draw normally:
Private Sub DrawRectangle(ByVal pnt As Point)
Try
Dim g As Graphics
g = Graphics.FromImage(img)
g.DrawImage(imgClone, 0, 0, pbZoneImage.Width, pbZoneImage.Height) 'we are clearing img with imgClone. imgClone contains the original image without the rectangles
Dim customPen As New Pen(currentColor, 5)
'If pnt.X = mouse_Down.X Or pnt.Y = mouse_Down.Y Then
' g.DrawLine(customPen, mouse_Down.X, mouse_Down.Y, pnt.X * w_ratio, pnt.Y * h_ratio)
'Else
theRectangle = New Rectangle(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))
g.DrawRectangle(customPen, theRectangle)
'End If
g.Dispose()
pbZoneImage.Invalidate() 'draw img to picturebox
Catch ex As Exception
End Try
End Sub
In mouse up event scale to get the correct result:
Private Sub pbZoneImage_MouseUp(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles pbZoneImage.MouseUp
Dim width, height As Integer
width = CInt(Math.Abs(mouse_Down.X - e.X) * (imgClone.Width / pbZoneImage.Width))
height = CInt(Math.Abs(mouse_Down.Y - e.Y) * (imgClone.Height / pbZoneImage.Height))
TextBox1.Text = width.ToString + " " + height.ToString
End Sub

Calculate coordinates for rotated text plus bounding border

I have a form that is going to allow a user to create custom "stamps" to place on a PDF. The form displays with a image of the first page of the pdf and I want the user to basically click on the screen where they want their stamp and be able to preview what its going to look like. Don't worry about any of the PDF stuff, I have that handled.
To make things snazzy, I have two copies of the image, the normal one and one with reduced brightness. I display the low brightness image and as the user moves the mouse over, a chunk of the original image is revealed or highlighted. I then display in that area the text the user is going to put on the PDF.
I allow the user to use the mousewheel to scroll and change the angle of the text they are placing (from -45 degrees to +45 degrees).
Here is my problem: I can't calculate the proper rectangles/coordinates. Sometimes everything looks great, other times (as font sizes change) they don't quite fit.
How do I calculate the x and y coordinates for:
placement of the rotated text
AND a bounding rectangle padding the text at its width and height with 10px
The code below works, until I start to crank up the font size, then everything gets out of skew.
First two images show text + bounding rectangle at smaller fonts. It looks good:
The next image shows that as the text size gets larger, my pixels are moving all around and gets chopped off. In even larger text, the widths/heights end being way off as well.
Sorry the example images don't show much detail. I have actual data that I can't share.
Private Sub PanelMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) '// handles the mouse move (handler added elsehwere)
With CType(sender, PictureBox)
.Image.Dispose() '// get rid of old image
Dim b As Bitmap = _curGray.Clone '// the low brightness image as the base image
'// stamp font and text values are initiated from another form
Using g As Graphics = Graphics.FromImage(b),
f As New Font(DefaultFont.FontFamily, CSng(_stmpTools.StampTextSize), If(_stmpTools.StampBold, FontStyle.Bold, FontStyle.Regular))
Const borderWidth As Integer = 10
Const borderPadding As Integer = 5
'// measure the string
Dim szx As SizeF = g.MeasureString(_stmpTools.StampText, f, Integer.MaxValue, StringFormat.GenericDefault)
Dim strLength As Single = szx.Width
Dim strHeight As Single = szx.Height
Dim x As Single = e.X - borderWidth - borderPadding,
y As Single = e.Y
Dim w As Double, h As Double
If Math.Abs(_angle) > Double.Epsilon Then
h = CDbl(strLength) * Math.Sin(CDbl(Math.Abs(_angle)) * Math.PI / 180.0F)
w = Math.Sqrt(CDbl(strLength) * CDbl(strLength) - h * h)
Else
'// its zero. so use calculated values
h = strHeight
w = strLength
End If
'// add space for the 10px border plus 5px of padding
Dim r As New Rectangle(0, 0, w, h)
r.Inflate(borderWidth + borderPadding, borderWidth + borderPadding)
h = r.Height
w = r.Width
'// keep box from moving off the left
If x < .Location.X Then
x = .Location.X
End If
'// keep box from moving off the right
If x > .Location.X + .Width - w Then
x = .Location.X + .Width - w
End If
'// I don't know, but these values work for most smaller fonts, but
'// it has got to be a fluke
If _angle > 0 Then
y = y - h + borderWidth + borderWidth
Else
y = y - borderWidth
End If
'// can't go off the top
If y < .Location.Y Then
y = .Location.Y
End If
'// can't go off the bottom
If y > .Location.Y + .Height - h Then
y = .Location.Y + .Height - h
End If
Dim rect As New Rectangle(x, y, w, h)
g.DrawImage(_curImg, rect, rect, GraphicsUnit.Pixel)
Using br As New SolidBrush(_stmpTools.StampTextColor)
RotateString(_stmpTools.StampText, _angle, e.X, e.Y, f, g, br)
End Using
'//draw bounding rectangle
Using p As New Pen(Color.Black, borderWidth)
g.DrawRectangle(p, rect)
End Using
End Using
'// set the picture box to show the new image
.Image = b
End With
End Sub
Private Sub RotateString(ByVal Text As String, ByVal angle As Integer, _
ByVal x As Integer, ByVal y As Integer, myfont As Font, mydrawing As Graphics, myColor As Brush)
Dim myMatrix As New Matrix
myMatrix.RotateAt(angle * -1, New Point(x, y)) 'Rotate drawing
mydrawing.Transform = myMatrix
mydrawing.DrawString(Text, myFont, myColor, x, y) 'Draw the text string
myMatrix.RotateAt(angle, New Point(x, y)) 'Rotate back
mydrawing.Transform = myMatrix
End Sub
I'm not the greatest when it comes to drawing. So any help would be great
Using the solution below from #LarsTech. I replaced the g.FillRectangle with:
g.DrawImage(_curImg, r, r, GraphicsUnit.Pixel)
_curImg is a copy of the original image with the brightness tuned up. When I change the code from below I end up with:
Note the double lines. They rotate with the stamp, even though they are acting as a background image and should be unrotated
Per suggestion, I changed the DrawStamp from #LarsTech to the following:
Private Sub DrawStamp(g As Graphics, text As String,
f As Font, center As Point, angle As Integer, backImg As Image)
Dim s As Size = g.MeasureString(text, f).ToSize
Dim r As New Rectangle(center.X - (s.Width / 2) - 16,
center.Y - (s.Height / 2) - 16,
s.Width + 32,
s.Height + 32)
g.DrawImage(backImg, r, r, GraphicsUnit.Pixel)
Using m As New Matrix
m.RotateAt(angle, center)
g.Transform = m
Using p As New Pen(Color.Black, 6)
g.DrawRectangle(p, r)
End Using
Using sf As New StringFormat
sf.LineAlignment = StringAlignment.Center
sf.Alignment = StringAlignment.Center
g.DrawString(text, f, Brushes.Black, r, sf)
End Using
g.ResetTransform()
End Using
End Sub
However, I am now left with
Notice it drew the background, then did the rotation and drew the stamp. It ALMOST works. In this example the straight lines show the intended behavior... however i'm looking to fill the entire stamp with the background. That extra white on the sides would have been what was rotated into the stamp's background. I'm confused because the 'grey' portions I would then suspect to be clipping out parts of the image, but they aren't (if i move it over other areas that I unfortunately can't post on here) notice is out of skew except for the fact that the sides of the rectangle paint as such.
Another Edit with hopefully some more info
Here is hopefully a better explaination of what I am trying to do. I use a third party PDF viewer and I need to allow the user to add an image to the PDF. The viewer doesn't allow me to raise click events on it, so in order to grab user mouse clicks, I do the following:
Take a screen grab of form
Hide the PDF Viewer
Add a PictureBox control to my form, replacing the area where the PDF viewer was
With my screen grab, I make a copy of the image with the brightness reduced
Display the gray scale copy of the image and draw directly on the image using mouse over events on the picturebox
I draw a stamp on the picturebox, but want the background of it to be the original (non adjusted brightness image). However, since the area might be transformed using a rotation, I can't grab the background image. If no angle is provided, the source rectangle matches. However if its rotated, I cannot grab the same rotated rectangle off the source image.
Button Click Event:
Dim bds As Rectangle = AxDPVActiveX1.Bounds
Dim pt As Point = AxDPVActiveX1.PointToScreen(bds.Location)
Using bit As Bitmap = New Bitmap(bds.Width, bds.Height)
Using g As Graphics = Graphics.FromImage(bit)
g.CopyFromScreen(New Point(pt.X - AxDPVActiveX1.Location.X, pt.Y - AxDPVActiveX1.Location.Y), Point.Empty, bds.Size)
End Using
_angle = 0
_curImg = bit.Clone
_curGray = Utils.CopyImageAndAdjustBrightness(bit, -100)
End Using
Dim p As New PictureBox
Utils.SetControlDoubleBuffered(p)
p.Dock = DockStyle.Fill
p.BackColor = Color.Transparent
AxDPVActiveX1.Visible = False
p.Image = _curImg.Clone
AddHandler p.MouseClick, AddressOf PanelDownMouse
AddHandler p.MouseMove, AddressOf PanelMouseMove
AddHandler p.MouseWheel, Sub(s As Object, ee As MouseEventArgs)
_angle = Math.Max(Math.Min(_angle + (ee.Delta / 30), 45), -45)
PanelMouseMove(s, ee)
End Sub
AddHandler p.MouseEnter, Sub(s As Object, ee As EventArgs)
CType(s, Control).Focus()
End Sub
AxDPVActiveX1.Parent.Controls.Add(p)
After that code I end up with two images. _curgray is an image with adjusted brightness, and _curImg is my original screen grab.
_curGray:
_curImg:
A mouseMove move event is applied to my new picture box. This is where all the code from earlier in the question comes into play.
Using the code above, my mouseMove event keeps creating a new imageto display in my picture box. If there is no rotation involved, I get pretty much what I'm looking for. Notice in the below image how the background of the stamp is brighter than everything. The portion over the blue square is slightly lighter. I am using this a way to draw the viewers eye to this area... its important for what I'm doing.
However, when applying a rotation to it, I cannot seem to copy from the original image. Look at the following image, the backgroundisn't rotating with it. I need to grab a rotated rectangle from the ORIGINAL image.
http://msdn.microsoft.com/en-us/library/ms142040(v=vs.110).aspx Graphics.DrawImage() accepts
Public Sub DrawImage ( _
image As Image, _
destRect As Rectangle, _
srcRect As Rectangle, _
srcUnit As GraphicsUnit _
)
where I can specify copy this source rectangle from my source image (in this case _curImg) and place onto my new drawing. It does not allow me to apply a transformation to the source rectangle. Basically I want to copy from my source image an area equivalent to the rotated rectangle (based on the transformation from #larstech )
I don't know how to express this concept any clearer. If it still doesn't make sense I will just accept LarsTech answer as the best answer and scrap my idea.
It's just trigonometry:
You know c because you know how wide the original text is, and you know h because you know the height of your text. You also need to know alpha, it's the angle that you rotated your text.
Now you need to work the power of math: First take the small rectangles at the end. In the bottom left you can see, that the angle right of the x is actually 180°-90°-alpha, or 90°-alpha. So alpha is also found on the opposite site. So you can find x:
x = h * sin(alpha)
The same goes for y, but it's either sin(90°-alpha), or cos(alpha)
y = h * cos(alpha)
Next you need to find a and b to complete the rectangle. The large triangle gives you
a = w * cos(alpha)
and
b = w * sin(alpha)
Then just add the parts together:
NewWidth = a + x
NewHeight = b + y
That way you get the size of the bounding box. As for the coordinates, it depends on which point is actually defined when you print the rotated text.
I would try drawing the rectangle and the text together:
Private Sub DrawStamp(g As Graphics, text As String,
f As Font, center As Point, angle As Integer)
Using m As New Matrix
g.SmoothingMode = SmoothingMode.AntiAlias
g.TextRenderingHint = TextRenderingHint.AntiAlias
m.RotateAt(angle, center)
g.Transform = m
Dim s As Size = g.MeasureString(text, f).ToSize
Dim r As New Rectangle(center.X - (s.Width / 2) - 16,
center.Y - (s.Height / 2) - 16,
s.Width + 32,
s.Height + 32)
g.FillRectangle(Brushes.White, r)
Using p As New Pen(Color.Black, 6)
g.DrawRectangle(p, r)
End Using
Using sf As New StringFormat
sf.LineAlignment = StringAlignment.Center
sf.Alignment = StringAlignment.Center
g.DrawString(text, f, Brushes.Black, r, sf)
End Using
g.ResetTransform()
End Using
End Sub
The paint example:
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
e.Graphics.Clear(Color.LightGray)
Using f As New Font("Calibri", 16, FontStyle.Bold)
DrawStamp(e.Graphics,
"Reviewed By Doctor Papa",
f,
New Point(Me.ClientSize.Width / 2, Me.ClientSize.Height / 2),
-25)
End Using
End Sub
Result:
Here I updated the code to "clip" the rotated rectangle so that I can copy that same area from the original image before applying the text and border:
Private Sub DrawStamp(g As Graphics, text As String,
f As Font, center As Point, angle As Integer)
Dim s As Size = g.MeasureString(text, f).ToSize
Dim r As New Rectangle(center.X - (s.Width / 2) - 16,
center.Y - (s.Height / 2) - 16,
s.Width + 32,
s.Height + 32)
Using bmp As New Bitmap(_curImg.Width, _curImg.Height)
Using gx As Graphics = Graphics.FromImage(bmp)
Using m As New Matrix
m.RotateAt(angle, center)
gx.Transform = m
gx.SetClip(r)
gx.ResetTransform()
End Using
gx.DrawImage(_curImg, Point.Empty)
End Using
g.DrawImage(bmp, Point.Empty)
End Using
Using m As New Matrix
g.SmoothingMode = SmoothingMode.AntiAlias
g.TextRenderingHint = TextRenderingHint.AntiAlias
m.RotateAt(angle, center)
g.Transform = m
Using p As New Pen(Color.Black, 6)
g.DrawRectangle(p, r)
End Using
Using sf As New StringFormat
sf.LineAlignment = StringAlignment.Center
sf.Alignment = StringAlignment.Center
g.DrawString(text, f, Brushes.Black, r, sf)
End Using
g.ResetTransform()
End Using
End Sub
New Result:

How to draw circular arcs in VB.NET

I have to generate the following figure according to user fed values. How do I go about drawing the arcs (B-C-F as in figure, circular in nature) given their start point & end point (B & F respectively) & the height from the segment BF? I can do some geometric calculations & get the radius & all, but how do I draw the arc?
I have tried using the Graphics.DrawCurve() method, but it doesn't work as expected. How can I make this method work for circular arcs? Any other workaround is also welcome.
From my comment:
If you have computed the necessary radius to generate the curve, then
simply draw the entire circle with Graphics.DrawEllipse(), but use
Graphics.SetClip() and pass a rectangle using the points B and F as a
side and computing the other two points using the height C. This will
clip the entire circle to just the part visible within that rectangle.
Then call Graphics.ResetClip() and draw the rest of the lines. Repeat
the SetClip() trick to draw the curve at the bottom as well.
Here's a proof of concept for the top curve thru B, C, and F.
I used the formulas provided by Donna Roberts at Investigative Circle Activity Using Three Points.
Here's a screenshot:
...and the code that produced it:
Public Class Form1
Private B As New Point(50, 100)
Private F As New Point(250, 100)
Private DistanceFromBF As Integer = 50
Private Sub Form1_Paint(sender As System.Object, e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
If B.Y = F.Y Then
Dim C As New Point(B.X + (F.X - B.X) / 2, B.Y - DistanceFromBF)
Dim ctr As Point
Dim rad As Double
CircleFromPointsOnCircumference(B, C, F, ctr, rad)
Dim rc As New Rectangle(ctr, New Size(1, 1))
rc.Inflate(rad, rad)
e.Graphics.DrawRectangle(Pens.Black, rc)
Dim clip As New Rectangle(New Point(B.X, B.Y - DistanceFromBF), New Size(F.X - B.X, DistanceFromBF))
e.Graphics.SetClip(clip)
e.Graphics.DrawEllipse(Pens.Green, rc)
e.Graphics.ResetClip()
DrawPoint(B, e.Graphics, Color.Red)
DrawPoint(C, e.Graphics, Color.Red)
DrawPoint(F, e.Graphics, Color.Red)
DrawPoint(ctr, e.Graphics, Color.Green)
End If
End Sub
Private Sub DrawPoint(ByVal pt As Point, ByVal G As Graphics, ByVal clr As Color)
Dim rc As New Rectangle(pt, New Size(1, 1))
rc.Inflate(3, 3)
Using brsh As New SolidBrush(clr)
G.FillEllipse(brsh, rc)
End Using
End Sub
Private Sub CircleFromPointsOnCircumference(ByVal ptA As Point, ByVal ptB As Point, ByVal ptC As Point, ByRef Center As Point, ByRef Radius As Double)
Dim mR As Double = CDbl(ptA.Y - ptB.Y) / CDbl(ptA.X - ptB.X)
Dim mT As Double = CDbl(ptC.Y - ptB.Y) / CDbl(ptC.X - ptB.X)
Dim X As Double = (mR * mT * (ptC.Y - ptA.Y) + mR * (ptB.X + ptC.X) - mT * (ptA.X + ptB.X)) / CDbl(2) * (mR - mT)
Dim Y As Double = CDbl(-1) / mR * (X - CDbl(ptA.X + ptB.X) / CDbl(2)) + (CDbl(ptA.Y + ptB.Y) / CDbl(2))
Center = New Point(X, Y)
Radius = Math.Sqrt(Math.Pow(ptA.X - Center.X, 2) + Math.Pow(ptA.Y - Center.Y, 2))
End Sub
End Class
Got it! Thanks #Mitch & #Idle_Mind
Using the builtin DrawArc method of Graphics
Friend Function draw_tank() As Boolean
' Create pen.
Dim blackPen As New Pen(Color.Black, 3)
' Create rectangle to bound ellipse.
Dim rect As New Rectangle(100, 100, 200, 200)
' Keeping the width & length same (200) we get a circle
' Create start and sweep angles on ellipse.
Dim startAngle As Single = 225.0F
Dim sweepAngle As Single = 90.0F
' Draw arc to screen.
Dim myarc As Graphics = Me.CreateGraphics
myarc.DrawArc(blackPen, rect, startAngle, sweepAngle)
Return True
End Function
Suggestions/Improvements welcome.
Note - This isn't the actual function from my code.

Drawing a septagon in vb.net

Does anyone have any code that can draw an equilateral septagon in vb.net?
All sides and angles need to be equal.
Thanks
No, but if you think of a clock face with 60 minutes, each 8.5 minutes marks one point of your 7-sided shape.
Here is a function to draw a regular polygon of specified number of sides:
Sub poly(ByVal center As PointF, ByVal radius As Double, ByVal nSides As Integer, ByVal g As Graphics)
Dim pts(nSides) As PointF
Dim Angle As Double = Math.PI * 2 / nSides
Dim i As Integer
Dim a As Double
a = Math.PI / 2 ' first point on top
For i = 0 To UBound(pts)
pts(i) = center + New Point(radius * Math.Cos(a), -radius * Math.Sin(a))
a = a + Angle
Next i
g.DrawPolygon(Pens.DarkGreen, pts)
End Sub
To call it, setup a graphics object where you want it drawn. For example, to draw it in PictureBox1, you could call it like this:
Dim g As Graphics
PictureBox1.Image = New Bitmap(PictureBox1.Width, PictureBox1.Height) ' new bitmap
g = Graphics.FromImage(PictureBox1.Image) ' assign graphics object to g
g.FillRectangle(Brushes.White, 0, 0, PictureBox1.Width, PictureBox1.Height) ' white background
' draw 7-sided polygon in the center of the picturebox
poly(New PointF(PictureBox1.Width / 2, PictureBox1.Height / 2), PictureBox1.Height / 3, 7, g)