Introduction
This is a simple demonstration of how to draw rotated text with or without an outline onto an image, and move it around with the mouse.
Background
This is a starter program which can easily grow with many options like multiple objects, save options, text change, color choice... depending on what you want to do with it, but I want to keep this example simple. I was working on a photo organization program, and wanted to have annotation as part of the program. I couldn't find a simple explanation to get started, so here it is.
Points of Interest
Drawing the Text
I used the GraphicsPath.AddString
and Graphics.DrawPath
methods instead of the Graphics.Drawstring
because I also wanted to be able to outline the text. DrawString
only fills in the text, but the GraphicsPath
can be filled in or just drawn as an outline. To rotate the text, the Matrix
structure's RotateAt
was used.
- Create a
Graphics
object (we'll call the canvas) from a copy of the image.
- Set
SmoothingMode
to AntiAlias
so the text isn't blocky.
- Create a
GraphicsPath
for the text.
- Rotate a
Matrix
by the given value at the center point of the text.
- Use
TransformPoints
to get the rotated corner points for the text bounds.
- Draw the path for the filled in text on the canvas.
- Draw the outline of the path for the text on the canvas.
- Now, you have an annotated image that can be drawn or saved how and where you wish.
Private Sub Form1_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles MyBase.Paint
Dim tbm As Bitmap = CType(bm.Clone, Bitmap)
Dim g As Graphics = Graphics.FromImage(tbm)
Dim mx As Matrix = New Matrix
Dim br As SolidBrush = New SolidBrush(Color.FromArgb(tbarTrans.Value, _
Color.LightCoral))
SetptsText()
g.SmoothingMode = SmoothingMode.AntiAlias
Dim emsize As Single = Me.CreateGraphics.DpiY * pic_font.SizeInPoints / 72
gpathText.AddString(strText, pic_font.FontFamily, CInt(pic_font.Style), _
emsize, New RectangleF(ptText.X, ptText.Y, szText.Width, szText.Height), _
StringFormat.GenericDefault)
g.DrawImage(CType(bm.Clone, Bitmap), 0, 0)
mx.RotateAt(tbarRotate.Value, _
New Point(ptText.X + (szText.Width / 2), _
ptText.Y + (szText.Height / 2)))
mx.TransformPoints(ptsText)
g.Transform = mx
g.FillPath(br, pathText)
If chkAddOutline.Checked Then
Using pn As Pen = New Pen(Color.FromArgb(tbarTrans.Value, Color.White), 1)
g.DrawPath(pn, pathText)
End Using
End If
If MouseOver Then
g.ResetTransform()
g.DrawPolygon(ptsTextPen, ptsText)
End If
e.Graphics.DrawImage(tbm, 10, 10)
tbm.Dispose()
g.Dispose()
mx.Dispose()
br.Dispose()
pathText.Dispose()
End Sub
Tracking the Text Location
To know if the mouse is over the text, I could have gotten the bounds for the rotated text and used the Rectangle.Contains(pt.x,pt.y)
in the mouse events, but that creates hits when you are not really over the actual text when it is rotated. To check if a point is over the text itself, take the corners of the rectangular bounds of the text as a point array and use a rotated Matrix
to transform the points. Create a Region
from a GraphicsPath
made from the the rotated points, and check if the mouse location is within the Region
with the IsVisible
method.
Public Function IsMouseOverText(ByVal X As Integer, ByVal Y As Integer) As Boolean
Using gp As New GraphicsPath()
gp.AddPolygon(ptsText)
Using TextRegion As New Region(gp)
Return TextRegion.IsVisible(X, Y)
End Using
End Using
End Function
Moving the Text
To be able to move the text, it has to be layered separate from the image. Each time the text is drawn, use a clean copy of the original image and draw the text in the new location. Use the MouseUp
, MouseDown
, and MouseMove
events, and the boolean MouseOver
and MouseMoving
variables and the MouseOffset
point variable.
In the MouseDown
event, flag the MouseMoving
variable and set the MouseOffset
point to the difference between the current cursor location and the upper left corner of the text.
Private Sub Form1_MouseDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles Me.MouseDown
If IsMouseOverText(e.X - 10, e.Y - 10) Then
MouseMoving = True
MovingOffset.X = e.X - ptText.X
MovingOffset.Y = e.Y - ptText.Y
Else
MouseMoving = False
End If
End Sub
If the mouse is moving, but the button is not pressed, set the MouseOver
flag so the selection box will be drawn or not depending on whether the mouse is over the text. If the button is pressed, set the upper left corner of the text to the new location minus the MouseOffset
.
Private Sub Form1_MouseMove(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
If IsMouseOverText(e.X - 10, e.Y - 10) Then
If Not MouseOver Then
MouseOver = True
Me.Refresh()
End If
Else
If MouseOver Then
MouseOver = False
Me.Refresh()
End If
End If
If e.Button = Windows.Forms.MouseButtons.Left And MouseMoving Then
ptText.X = CInt(e.X - MovingOffset.X)
ptText.Y = CInt(e.Y - MovingOffset.Y)
Me.Refresh()
End If
End Sub
If the mouse button is released, set MouseMoving
to False
.
Private Sub Form1_MouseUp(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
MouseMoving = False
Me.Refresh()
End Sub
Enjoy!
History
- Version 1.0 - June 2008 - First trial.
- Version 2.0 - July 2008 - While fixing a bug in determining if the mouse is over the text, I figured out a simpler method of checking if the mouse is inside the text region.