Click here to Skip to main content
15,885,767 members
Articles / Multimedia / GDI+
Article

Drawing and Editing Lines with GDI+

Rate me:
Please Sign up or sign in to vote.
4.82/5 (46 votes)
20 Oct 2004CPOL3 min read 248.4K   6.9K   87   48
Here's the code to implement a basic graphic user interface to paint, and then edit, lines on a PictureBox.

Sample Image - lineditor.jpg

Introduction

My first article! I hope that this code will help someone... I searched through the web and I found many developers (like me) looking for a way to implement line drawing/editing, that seems to be a thing very hard to implement, due to C# limitations in this topic... GDI+ doesn't seem to come in help very much, and I decided to make a solution by myself, that seems to work pretty fine :D. Here it is.

Once Upon a Time...

I started thinking about a Line Custom Control, but the result was very ugly, due to problems about transparency and Z-order (you cannot select a control that's under another one)... I then realized that the line itself had to be written directly to the PictureBox in order to avoid these problems, and the PictureBox.Image must be rewritten every time...

When the DrawLine button has been clicked, two red markers are placed in the PictureBox and a line is drawn. The two markers are actually two custom controls, inherited from UserControl, but they simply are a red square (that can be personalized, a cross, a circle, etc,), with no code inside. At the marker control creation, I attach three mouse events (MouseDown, MouseMove, MouseUp) in order to allow user to drag them around the PictureBox. Each time a marker is moved, all the lines (and the background image too) are redrawn. Very simple, isn't it?

Let's see some code...

C#
private struct Line
{
    public MarkControl mark1;
    public MarkControl mark2;
    public int Width;
}

This object represents a line drawn on the PictureBox. It contains information about the two markers (their position, property Center) and the line width (editable through right mouse buttons). Each line drawn is added to an array of Lines; each time the image is redrawn, all the lines are redrawn using this info.

C#
//Redraws all the lines and the background too
private void Redraw() 
{
    if(bmpBack!=null)
        image.Image = (Bitmap)bmpBack.Clone();
    else
    {
        image.Image = new Bitmap(image.Width,image.Height);
        Graphics.FromImage(image.Image).Clear(Color.White);
    }

    foreach(Line l in Lines)
    {
        DrawLine(l);
    }
    image.Refresh(); 
}

And this is the DrawLine function:

C#
//Simply draws a line
private void DrawLine(Line line)
{
    Graphics g = null;

    g = Graphics.FromImage(image.Image);
    g.DrawLine(new Pen(Color.Black,(float)line.Width), 
               line.mark1.Center.X,line.mark1.Center.Y, 
               line.mark2.Center.X,line.mark2.Center.Y); 
    g.Dispose();
}

Amazing, it works! But there is a problem... When drawing lines on a big surface, like a desktop background, the program runs very slow, because the image is too heavy to be reloaded all the time... Then? I wrote a new Redraw(), that redraws all the lines in the same way, but refreshes only the region of the image that has been modified, through the Invalidate(Region region) method of the PictureBox... This trick code has to be a little enhanced, but it works very fine for now! If someone has a better idea, please tell me! Here's the code:

C#
//Redraws all the lines and a part of the background
private void Redraw(Line line, Point p)
{
    Graphics.FromImage(image.Image).DrawImage(bmpBack,0,0, 
                       image.Image.Width, image.Image.Height);
    foreach(Line l in Lines)
    {
        DrawLine(l);
    }
    Region r = getRegionByLine(line,p);
    image.Invalidate(r);
    image.Update();
}

//Returns the region to update
private Region getRegionByLine(Line l, Point p)
{
    GraphicsPath gp = new GraphicsPath();
    gp.AddPolygon(new Point[]{l.mark1.Center,l.mark2.Center,p,l.mark1.Center});

    RectangleF rf = gp.GetBounds();
    gp.Dispose();

    rf.Inflate(100f,100f);

    return new Region(rf);
}

Line l is the line that has been moved, p is the Point where the marker has been moved... I calculate the region getting the triangle from the line and the point, then getting a rectangle that contains it, and then inflating it for a better result...

Lines Only?

This project can be modified as well, for other geometric figures like Circles, Ellipses, Rectangles, Polygons, and so on! Add more markers and use them as geometric points!

The End

Yeah, that's all folks ;) I hope that this article will help someone as other Code Project articles have helped me in my work everyday. Please send me feedbacks or/and advice! butch.er@tin.it.

Ciao!

License

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


Written By
Software Developer (Senior)
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Yo Hiro4-Dec-13 14:23
Yo Hiro4-Dec-13 14:23 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey21-Feb-12 23:44
professionalManoj Kumar Choubey21-Feb-12 23:44 
Questioncan i do the same in panel?????? Pin
himani_jha3-Jan-12 21:39
himani_jha3-Jan-12 21:39 
AnswerRe: can i do the same in panel?????? Pin
Butch.er3-Jan-12 22:31
Butch.er3-Jan-12 22:31 
GeneralMy vote of 5 Pin
clingonboy5-Nov-11 2:43
clingonboy5-Nov-11 2:43 
GeneralMy vote of 5 Pin
jraju1142116-Oct-11 23:18
jraju1142116-Oct-11 23:18 
GeneralVertex image Pin
ISharda25-May-10 0:56
ISharda25-May-10 0:56 
GeneralRe: Vertex image Pin
Butch.er25-May-10 1:01
Butch.er25-May-10 1:01 
GeneralAWESOME!!!!!!!!!! Pin
Reidel2-Jan-10 6:32
Reidel2-Jan-10 6:32 
JokeRe: AWESOME!!!!!!!!!! Pin
Butch.er2-Jan-10 10:49
Butch.er2-Jan-10 10:49 
GeneralText Wraping in MarkControl Pin
rizwaanbutt31-Dec-07 19:06
rizwaanbutt31-Dec-07 19:06 
GeneralRe: Text Wraping in MarkControl Pin
Butch.er13-Feb-08 23:57
Butch.er13-Feb-08 23:57 
QuestionProblem with: Picturebox size Mode & Marks Pin
xupi11-Jul-07 23:59
xupi11-Jul-07 23:59 
AnswerRe: Problem with: Picturebox size Mode & Marks Pin
Butch.er12-Jul-07 5:28
Butch.er12-Jul-07 5:28 
QuestionRe: Problem with: Picturebox size Mode & Marks Pin
xupi12-Jul-07 7:00
xupi12-Jul-07 7:00 
QuestionNeed more Help Pin
rizwaanbutt22-May-07 21:14
rizwaanbutt22-May-07 21:14 
QuestionNeed your help to make it more faster Pin
rizwaanbutt13-May-07 20:22
rizwaanbutt13-May-07 20:22 
AnswerRe: Need your help to make it more faster Pin
Butch.er13-May-07 23:30
Butch.er13-May-07 23:30 
QuestionRe: Need your help to make it more faster Pin
rizwaanbutt17-May-07 19:09
rizwaanbutt17-May-07 19:09 
QuestionRe: Need your help to make it more faster Pin
junzhi_li22-Jul-07 5:12
junzhi_li22-Jul-07 5:12 
Hello,Mr.Dbutch

I repair your code,now I can draw a line and code below.now,how to change line's color when mouse move to line above ,and how to open a form when mouse click line.Thank you very mouch .

code:

/*
* *******************************
* Line Editor by Marco Bellini *
* *
* Please send me feedbacks! : *
* Dbutch.er@tin.it *
* *
* ...and share Knowledge! *
* *
* *******************************
*/


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

using System.IO;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;


namespace LineEditor
{

public class Form1 : System.Windows.Forms.Form
{
private IContainer components;
private System.Windows.Forms.PictureBox image;
private System.Windows.Forms.Button button1;

Bitmap bmpBack = null;
private bool isSelected = false;
private int _X, _Y;
private ArrayList Lines = null;


private struct Line
{
public MarkControl mark1;
public MarkControl mark2;
public int Width;
}


public Form1()
{
InitializeComponent();

Lines = new ArrayList();
bmpBack = new Bitmap(image.Width, image.Height);
Graphics.FromImage(bmpBack).Clear(Color.White);
image.Image = (Bitmap)bmpBack.Clone();

this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}

}
base.Dispose(disposing);
}

#region Windows Form Designer generated code
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.image = new System.Windows.Forms.PictureBox();
this.button1 = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.image)).BeginInit();
this.SuspendLayout();
//
// image
//
this.image.BackColor = System.Drawing.SystemColors.ActiveCaptionText;
this.image.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.image.Location = new System.Drawing.Point(19, 17);
this.image.Name = "image";
this.image.Size = new System.Drawing.Size(480, 431);
this.image.TabIndex = 3;
this.image.TabStop = false;

// button1
//
this.button1.Location = new System.Drawing.Point(509, 17);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(134, 43);
this.button1.TabIndex = 11;
this.button1.Text = "Draw Line";
this.button1.Click += new System.EventHandler(this.button1_Click);
//

// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.AutoScroll = true;
this.BackColor = System.Drawing.SystemColors.Control;
this.ClientSize = new System.Drawing.Size(657, 242);
this.Controls.Add(this.button1);
this.Controls.Add(this.image);
this.Name = "Form1";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Line Editor";
((System.ComponentModel.ISupportInitialize)(this.image)).EndInit();
this.ResumeLayout(false);

}
#endregion


[STAThread]
static void Main()
{
Application.Run(new Form1());
}

private void button1_Click(object sender, System.EventArgs e)
{
try
{
//Adds red marks that are the beginning/end of the line
MarkControl mark1 = new MarkControl();
mark1.Location = new Point(50, 50);
image.Controls.Add(mark1);

MarkControl mark2 = new MarkControl();
mark2.Location = new Point(100, 100);
image.Controls.Add(mark2);

//Line Struct contains the information for a single line
Line line = new Line();
line.mark1 = mark1;
line.mark2 = mark2;
line.Width = 1;


//Events for moving marks
mark1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseUp);
mark1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseDown);
mark1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseMove);

mark2.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseUp);
mark2.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseDown);
mark2.MouseMove += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseMove);

//Adds Line object to an arraylist
Lines.Add(line);
Redraw();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}


//Simply draws a line
private void DrawLine(Line line)
{
Graphics g = null;

g = Graphics.FromImage(image.Image);
g.DrawLine(new Pen(Color.Black, (float)line.Width), line.mark1.Center.X, line.mark1.Center.Y, line.mark2.Center.X, line.mark2.Center.Y);
g.Dispose();
}

//Redraws all the lines and a part of the background
private void Redraw(Line line, Point p)
{


Graphics.FromImage(image.Image).DrawImage(bmpBack, 0, 0, image.Image.Width,
image.Image.Height);

foreach (Line l in Lines)
{
DrawLine(l);
}
image.Update();
}


//Redraws all the lines and the background too
private void Redraw()
{
if (bmpBack != null)
image.Image = (Bitmap)bmpBack.Clone();
else
{
image.Image = new Bitmap(image.Width, image.Height);
Graphics.FromImage(image.Image).Clear(Color.Transparent);
}

foreach (Line l in Lines)
{
DrawLine(l);
}
image.Refresh();
}

private void Mark_MouseDown(object sender, MouseEventArgs e)
{
this.SuspendLayout();
isSelected = true;
_X = e.X;
_Y = e.Y;
}

private void Mark_MouseMove(object sender, MouseEventArgs e)
{
if (isSelected)
{
MarkControl mc1 = (MarkControl)sender;
Line l = getLineByMark(mc1);

Point p = new Point(e.X - _X + mc1.Left, e.Y - _Y + mc1.Top);

mc1.Location = p;

Redraw(l, p);
}
}

private void Mark_MouseUp(object sender, MouseEventArgs e)
{
isSelected = false;
ResumeLayout();
Redraw();
}

//Retrieves a mark having the other one
private MarkControl getOtherMark(MarkControl m)
{
foreach (Line l in Lines)
{
if (l.mark1 == m)
return l.mark2;

if (l.mark2 == m)
return l.mark1;
}//Never happens Big Grin | :-D
throw new Exception("No relative mark found");
}

//Retrieves a Line object having a mark
private Line getLineByMark(MarkControl m)
{
foreach (Line l in Lines)
{
if (l.mark1 == m || l.mark2 == m)
return l;
}//Never happens Big Grin | :-D
throw new Exception("No line found");
}

public class MarkControl : UserControl
{
private Container components = null;

public MarkControl()
{
InitializeComponent();
this.BackColor = Color.Red;
}

public Point Center
{
get { return new Point(Location.X + 4, Location.Y + 4); }
}

protected override void OnPaint(PaintEventArgs e)
{
//
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}

#region Component Designer generated code

private void InitializeComponent()
{
this.SuspendLayout();
//
// MarkControl
//
this.Name = "MarkControl";
this.Size = new System.Drawing.Size(9, 9);
this.ResumeLayout(false);

}
#endregion

}

}
}

My email:junzhi_li@hotmail.com & remi_lijunzhi@yahoo.com.cn
evryone can solve it ,thank you very much!!



strong man,Aha.

GeneralResizable graphic area Pin
GrusumZA12-Feb-07 23:26
GrusumZA12-Feb-07 23:26 
GeneralRe: Resizable graphic area Pin
Butch.er12-Feb-07 23:43
Butch.er12-Feb-07 23:43 
GeneralRe: Resizable graphic area Pin
GrusumZA12-Feb-07 23:59
GrusumZA12-Feb-07 23:59 
GeneralRe: Resizable graphic area Pin
GrusumZA13-Feb-07 19:51
GrusumZA13-Feb-07 19:51 
GeneralI do not think so, it is not as good as it tis Pin
nn813719-Nov-06 1:35
nn813719-Nov-06 1:35 

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.