Click here to Skip to main content
15,889,867 members
Articles / Multimedia / GDI+
Article

Simple Image Annotation - How to dynamically add rotated text to an image

Rate me:
Please Sign up or sign in to vote.
4.85/5 (11 votes)
2 Jul 2008CPOL3 min read 68K   1.9K   23   31
Add outlined text to an image, rotate it, and move it around with the mouse.

Image 1

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.

  1. Create a Graphics object (we'll call the canvas) from a copy of the image.
  2. Set SmoothingMode to AntiAlias so the text isn't blocky.
  3. Create a GraphicsPath for the text.
  4. Rotate a Matrix by the given value at the center point of the text.
  5. Use TransformPoints to get the rotated corner points for the text bounds.
  6. Draw the path for the filled in text on the canvas.
  7. Draw the outline of the path for the text on the canvas.
  8. Now, you have an annotated image that can be drawn or saved how and where you wish.
VB
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))

    'Set the Points for the Text region        
    SetptsText()

    'Smooth the Text
    g.SmoothingMode = SmoothingMode.AntiAlias

    'Make the GraphicsPath for the Text
    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)

    'Draw a copy of the image to the Graphics Object canvas
    g.DrawImage(CType(bm.Clone, Bitmap), 0, 0)

    'Rotate the Matrix at the center point
    mx.RotateAt(tbarRotate.Value, _
        New Point(ptText.X + (szText.Width / 2), _
        ptText.Y + (szText.Height / 2)))

    'Get the points for the rotated text bounds
    mx.TransformPoints(ptsText)

    'Transform the Graphics Object with the Matrix
    g.Transform = mx

    'Draw the Rotated Text
    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

    'Draw the box if the mouse is over the Text
    If MouseOver Then
        g.ResetTransform()
        g.DrawPolygon(ptsTextPen, ptsText)
    End If

    'Draw the whole thing to the form
    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.

VB
Public Function IsMouseOverText(ByVal X As Integer, ByVal Y As Integer) As Boolean
 'Make a Graphics Path from the rotated ptsText. 
 Using gp As New GraphicsPath()
     gp.AddPolygon(ptsText)

     'Convert to Region.
     Using TextRegion As New Region(gp)
         'Is the point inside the region.
         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.

VB
Private Sub Form1_MouseDown(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.MouseEventArgs) _
            Handles Me.MouseDown

    'Check if the pointer is over the Text
    If IsMouseOverText(e.X - 10, e.Y - 10) Then
        MouseMoving = True
        'Determine the upper left corner point 
        'from where the mouse was clicked
        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.

VB
Private Sub Form1_MouseMove(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove

    'Check if the pointer is over the Text
    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.

VB
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
I first got hooked on programing with the TI994A. After it finally lost all support I reluctantly moved to the Apple IIe. Thank You BeagleBros for getting me through. I wrote programs for my Scuba buisness during this time. Currently I am a Database manager and software developer. I started with VBA and VB6 and now having fun with VB.NET/WPF/C#...

Comments and Discussions

 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey21-Feb-12 23:37
professionalManoj Kumar Choubey21-Feb-12 23:37 
Questionhow to move multiple objects seperately? Pin
MorganRock9-Sep-11 4:59
MorganRock9-Sep-11 4:59 
AnswerRe: how to move multiple objects seperately? Pin
SSDiver21129-Sep-11 6:02
SSDiver21129-Sep-11 6:02 
QuestionHow to save the position of the rotated text Pin
Dave Franco2-Jul-11 2:00
Dave Franco2-Jul-11 2:00 
AnswerRe: How to save the position of the rotated text Pin
SSDiver21126-Jul-11 12:54
SSDiver21126-Jul-11 12:54 
GeneralRe: How to save the position of the rotated text Pin
Dave Franco6-Jul-11 20:50
Dave Franco6-Jul-11 20:50 
GeneralRe: How to save the position of the rotated text Pin
SSDiver21129-Jul-11 6:06
SSDiver21129-Jul-11 6:06 
QuestionRegarding Size of the Image Pin
Dave Franco24-Jun-11 6:43
Dave Franco24-Jun-11 6:43 
QuestionImage Resize Pin
Dave Franco21-Jun-11 12:04
Dave Franco21-Jun-11 12:04 
AnswerRe: Image Resize Pin
SSDiver211221-Jun-11 14:20
SSDiver211221-Jun-11 14:20 
GeneralRe: Image Resize Pin
Dave Franco21-Jun-11 23:21
Dave Franco21-Jun-11 23:21 
QuestionHow to Change font using Font Dialog? [modified] Pin
Dave Franco20-Jun-11 22:30
Dave Franco20-Jun-11 22:30 
AnswerRe: How to Change font using Font Dialog? Pin
SSDiver211221-Jun-11 3:20
SSDiver211221-Jun-11 3:20 
GeneralRe: How to Change font using Font Dialog? Pin
Dave Franco21-Jun-11 5:04
Dave Franco21-Jun-11 5:04 
AnswerRe: How to Change font using Font Dialog? Pin
SSDiver211221-Jun-11 3:43
SSDiver211221-Jun-11 3:43 
As a someday article I have been working on and off, on a better version that allows you to drop multiple text objects on the picture and rotate and size each independently with the mouse. I just have to find the time to finish it.

For now though replace the form code in this article with the code below. Add the font size Combobox or the Font Dialog as you see fit and tweak that part of the code. This will let you use the mouse to rotate the text directly.


VB
Imports System.Drawing.Drawing2D

Public Class Form1
    Dim rAngle As Integer
    Dim sAngle As Integer
    Dim pic_font As Font
    Dim bm As Bitmap
    Dim tbm As Bitmap
    Dim strText As String = "Diver Dude"
    Dim szText As New SizeF
    Dim ptText As New Point(125, 125)
    Dim ptsAngle() As PointF
    Dim ptOrigin As PointF
    Dim ptsText() As PointF
    Dim ptsRotateText() As PointF
    Dim ptsTextPen As Pen = New Pen(Color.LightSteelBlue, 1)
    Dim MovingOffset As PointF
    Dim MouseMoving As Boolean
    Dim MouseRotating As Boolean
    Dim MouseOver As Boolean

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        Me.SetStyle(ControlStyles.DoubleBuffer, True)
    End Sub

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        ptsTextPen.DashStyle = DashStyle.Dot
        bm = My.Resources.DivePic
        'bm = Image.FromFile(Application.StartupPath & "\DivePic.bmp")

        Dim FSize() As Single = {4, 6, 8, 10, 12, 13, 14, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 46, 50, 56, 60, 72, 80}
        Dim FS As Single
        For Each FS In FSize
            cboFontSize.Items.Add(FS)
        Next
        cboFontSize.SelectedIndex = cboFontSize.FindString("40")

        pic_font = New Font("Arial Black", CSng(cboFontSize.Text), FontStyle.Regular, GraphicsUnit.Pixel)
        szText = Me.CreateGraphics.MeasureString(strText, pic_font)
        SetptsText()


    End Sub

    Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown

        'Check if the pointer is over the Text
        If IsMouseOverRotate(e.X - 10, e.Y - 10) Then
            MouseRotating = True
            ptOrigin = New PointF(ptText.X + (szText.Width / 2), ptText.Y + (szText.Height / 2))
            sAngle = getAngle(ptOrigin, e.Location) - rAngle

        ElseIf IsMouseOverText(e.X - 10, e.Y - 10) Then
            MouseMoving = True
            'Determine the upper left corner point from where the mouse was clicked
            MovingOffset.X = e.X - ptText.X
            MovingOffset.Y = e.Y - ptText.Y
        Else
            MouseMoving = False
            MouseRotating = False
        End If

    End Sub

    Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove

        If e.Button = Windows.Forms.MouseButtons.Left Then
            If MouseMoving Then
                ptText.X = CInt(e.X - MovingOffset.X)
                ptText.Y = CInt(e.Y - MovingOffset.Y)
                Me.Invalidate()
            ElseIf MouseRotating Then
                rAngle = getAngle(ptOrigin, e.Location) - sAngle
                Me.Invalidate()
                lblRotate.Text = getAngle(ptOrigin, ptsAngle(0))
                lblRotate.Refresh()

            End If
        Else
            If IsMouseOverRotate(e.X - 10, e.Y - 10) Then
                'Check if the pointer is over the Text
                Me.Cursor = Cursors.Hand
                If Not MouseOver Then
                    MouseOver = True
                    Me.Invalidate()
                End If
            ElseIf IsMouseOverText(e.X - 10, e.Y - 10) Then
                Me.Cursor = Cursors.SizeAll
                If Not MouseOver Then
                    MouseOver = True
                    Me.Invalidate()
                End If
            Else
                Me.Cursor = Cursors.Default
                If MouseOver Then
                    MouseOver = False
                    Me.Invalidate()
                End If
            End If
        End If
    End Sub

    Private Function getAngle(ByVal Origin As PointF, ByVal XYPoint As PointF) As Integer

        Dim xLength As Single = XYPoint.X - Origin.X
        Dim yLength As Single = XYPoint.Y - Origin.Y
        Dim TheAngle As Single

        'On the Origin
        If xLength = 0 And yLength = 0 Then Return 0
        'On one of the Axis
        If xLength = 0 And yLength < 0 Then Return 0
        If yLength = 0 And xLength > 0 Then Return 90
        If xLength = 0 And yLength > 0 Then Return 180
        If yLength = 0 And xLength < 0 Then Return 270

        TheAngle = Math.Atan(xLength / yLength)
        TheAngle = TheAngle * (180 / Math.PI)

        'Adjust for the Quadrant
        If yLength > 0 Then
            'Quadrant 1 or 2
            TheAngle = 180 - TheAngle
        ElseIf xLength > 0 Then
            'Quadrant 0
            TheAngle = Math.Abs(TheAngle)
        ElseIf xLength < 0 Then
            'Quadrant 3
            TheAngle = 360 - TheAngle
        End If
        Return CInt(TheAngle)
    End Function

    Private Sub Form1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
        MouseMoving = False
        MouseRotating = False
        Me.Invalidate()
    End Sub

    Public Function IsMouseOverText(ByVal X As Integer, ByVal Y As Integer) As Boolean
        'Make a Graphics Path from the rotated ptsText.
        Using gp As New GraphicsPath()
            gp.AddPolygon(ptsText)

            Return gp.IsVisible(X, Y)

        End Using
    End Function

    Public Function IsMouseOverRotate(ByVal X As Integer, ByVal Y As Integer) As Boolean
        'Make a Graphics Path from the rotated ptsText.
        Using gp As New GraphicsPath()
            gp.AddPolygon(ptsRotateText)

            Return gp.IsVisible(X, Y)

        End Using
    End Function

    Private Sub Form1_Paint(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.PaintEventArgs) _
        Handles MyBase.Paint

        tbm = CType(bm.Clone, Bitmap)
        Dim g As Graphics = Graphics.FromImage(tbm)
        Dim mx As Matrix = New Matrix
        Dim gpathText As New GraphicsPath
        Dim br As SolidBrush = New SolidBrush(Color.FromArgb(tbarTrans.Value, _
                                              Color.LightCoral))

        'Set the Points for the Rectangle around the Text        
        SetptsText()

        'Smooth the Text
        g.SmoothingMode = SmoothingMode.AntiAlias

        'Make the GraphicsPath for the Text
        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)

        'Draw a copy of the image to the Graphics Object canvas
        g.DrawImage(CType(bm.Clone, Bitmap), 0, 0)

        'Rotate the Matrix at the center point
        mx.RotateAt(rAngle, _
            New Point(ptText.X + (szText.Width / 2), ptText.Y + (szText.Height / 2)))

        'Rotate the points for the text bounds
        mx.TransformPoints(ptsText)
        mx.TransformPoints(ptsRotateText)
        mx.TransformPoints(ptsAngle)

        'Transform the Graphics Object with the Matrix
        g.Transform = mx

        'Draw the Rotated Text
        g.FillPath(br, gpathText)
        If chkAddOutline.Checked Then
            Using pn As Pen = New Pen(Color.FromArgb(tbarTrans.Value, Color.White), 3)
                g.DrawPath(pn, gpathText)
            End Using
        End If

        'Draw the box if the mouse is over the Text
        If MouseOver Then
            g.ResetTransform()
            g.DrawPolygon(ptsTextPen, ptsText)
            g.FillPolygon(New SolidBrush(Color.FromArgb(100, Color.White)), ptsRotateText)
        End If

        'Draw the whole thing to the form
        e.Graphics.DrawImage(tbm, 10, 10)

        'tbm.Dispose()
        g.Dispose()
        mx.Dispose()
        br.Dispose()
        gpathText.Dispose()
    End Sub

    Private Sub TrackBar_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) _
      Handles tbarTrans.Scroll
        lblOpacity.Text = tbarTrans.Value
        Me.Invalidate()
    End Sub

    Sub SetptsText()
        'Create a point array of the Text Rectangle
        ptsText = New PointF() { _
            ptText, _
            New Point(CInt(ptText.X + szText.Width), ptText.Y), _
            New Point(CInt(ptText.X + szText.Width), CInt(ptText.Y + szText.Height)), _
            New Point(ptText.X, CInt(ptText.Y + szText.Height)) _
            }

        ptsRotateText = New PointF() { _
            New Point(CInt(ptText.X + szText.Width - 10), ptText.Y), _
            New Point(CInt(ptText.X + szText.Width), ptText.Y), _
            New Point(CInt(ptText.X + szText.Width), CInt(ptText.Y + 10)), _
            New Point(CInt(ptText.X + szText.Width - 10), CInt(ptText.Y + 10)) _
            }
        ptsAngle = New PointF() {New PointF(CInt(ptText.X + szText.Width), CInt(ptText.Y + (szText.Height / 2)))}
    End Sub

    Private Sub chkAddOutline_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles chkAddOutline.CheckedChanged
        Me.Invalidate()
    End Sub

    Private Sub cboFontSize_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboFontSize.SelectedIndexChanged
        pic_font = New Font("Arial Black", CSng(cboFontSize.Text), FontStyle.Regular, GraphicsUnit.Pixel)
        szText = Me.CreateGraphics.MeasureString(strText, pic_font)
        SetptsText()
        Me.Invalidate()
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Button1.Click
        tbm.Save("C:\Test.bmp")
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        If FontDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
            pic_font = FontDialog1.Font
            szText = Me.CreateGraphics.MeasureString(strText, pic_font)
            SetptsText()
            Me.Invalidate()
        End If
    End Sub
End Class

GeneralRe: How to Change font using Font Dialog? Pin
Dave Franco21-Jun-11 5:54
Dave Franco21-Jun-11 5:54 
GeneralRe: How to Change font using Font Dialog? Pin
SSDiver211221-Jun-11 7:23
SSDiver211221-Jun-11 7:23 
QuestionHow to save? Pin
Dave Franco28-May-11 0:00
Dave Franco28-May-11 0:00 
AnswerRe: How to save? Pin
SSDiver211229-May-11 18:49
SSDiver211229-May-11 18:49 
GeneralRe: How to save? [modified] Pin
Dave Franco29-May-11 22:39
Dave Franco29-May-11 22:39 
GeneralRe: How to save? Pin
SSDiver211230-May-11 14:25
SSDiver211230-May-11 14:25 
GeneralRe: How to save? Pin
Dave Franco30-May-11 20:29
Dave Franco30-May-11 20:29 
GeneralRe: How to save? Pin
SSDiver211231-May-11 2:13
SSDiver211231-May-11 2:13 
GeneralRe: How to save? [modified] Pin
Dave Franco31-May-11 3:27
Dave Franco31-May-11 3:27 
GeneralRe: How to save? Pin
SSDiver211231-May-11 6:58
SSDiver211231-May-11 6:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.