Click here to Skip to main content
Click here to Skip to main content

Simple Vector Shapes

, 9 Jun 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
A 2D vector shapes and RTFcontrol editor
Screenshot - vectshapes1.jpg

Introduction

Hello. This is my 3rd article, after this one and this one. I propose a simple 2D vector graphic editor. It manages simple graphic objects like rectangles, lines and ellipses, other than images and RTF text. It shows most of the capabilities of GDI+, such as color transparency, pen style, start/end line cap and so on. All of the images shown in this article have been realized with the tool I propose.

Using the Code

Screenshot - vectshapes2.jpg

VectShape is the UserControl used to show and manipulate the objects. It contains the mouse event logic and the "extended" double buffer logic for rendering. It exposes a big set of methods. These methods are used mainly by toolBox, which is the control used to manipulate the shapes. Shapes is a collection of Ele. Ele is the base class for all of the objects: rectangles, boxText, Lines, Ellipses and imageBoxes. RichForm2 is the form used to edit RTF text. It's a simple form with a rich text box and a tool strip. Go to here for a better example of an RTF editor.

GDI+

When searching The Code Project for GDI+, you'll find many good articles that explain what GDI+ is and the basis of drawing in .NET. In this article, I do not address these basic problems. An in-depth article on GDI+ can be found here.

Add Controls to Your Own Form

Simply put the controls vectShaper and toolBox into your form. Then link toolBox to vectShaper in the form (i.e. Form1) constructor:

public Form1()
{
    InitializeComponent();

    // added for linking toolbox to vectshapes
    this.toolBox1.setVectShape(this.vectShapes1);            
}

Now you're ready to start drawing some shapes.

Points of Interest

Drawing Objects: Extending Double Buffer

You can find many in-depth articles about Double Buffering (DB) on The Code Project. With the following 2 images, I will demonstrate how double buffering works. The idea of DB is to make the drawing on a memory bitmap buffer and then draw the buffer over the control.

Screenshot - vectshapes3.jpg Screenshot - vectshapes4.jpg

In this project, I extended the double buffer with two memory buffers. One is for drawing static objects and one is for storing dynamic/moving objects. See the next image:

Screenshot - vectshapes5.jpg
  1. backBuffG=Graphics.fromimage(backBuff)
  2. draws static objects over backBuffG
  3. buffG=Graphics.fromimage(buff)
  4. draws bitmap backBuffG over buffG
  5. draws moving objects over buffG
  6. ctrG=ctr.CreateGraphics()
  7. draws bitmap buffG over ctrG

backBuff is declared outside of the control redraw method. By STATIC objects, I mean all objects that we know aren't going to be moved/added/deleted/updated. In this kind of application, all of the objects that are not selected are static objects. Steps 1 and 2 are done only when we update the static objects. So, we don't redraw static objects in the paint method, but only the bitmap that "contains" them (Step 4).

Screenshot - vectshapes7.jpg

In this example, the blue objects are static. The selected objects -- the green selection rectangle and the handle rectangle -- are all dynamic/moving objects. So when I add/remove/update an object, such as in the MouseUp event of the control, I redraw the control including Steps 1 and 2. When I move a selected object -- i.e. in the MouseMove event or in the OnPaint event of the control -- I do not need to perform Steps 1 and 2. The static objects are drawn because I stored them in backBuff.

Rendering RichTextBox (RTF) on a Graphic Object

Another interesting point in this project is the rendering of the RTF contained in a rich text box. The Graphics.DrawString method lets you do something like that, but with a significant limitation. You can specify only one font, color or dimension in the same string. So, it does not draw truly RTF text. I was interested in rendering RTF text, so I Googled it and found this. It explains how to print the RTF contained in a rich text box control. The article contains the code for creating RichTextBoxPrintCtrl and demonstrates how to use it:

[DllImport("USER32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, 
    IntPtr wp, IntPtr lp);

// I extended the RichTextBoxPrintCtrl code by adding a Draw method:

public int Draw(int charFrom, int charTo, Graphics g, 
    int x, int y, int x1, int y1, double conversion)
{
    //Calculate the area to render and print
    RECT rectToPrint;
    rectToPrint.Top =(int)(y * conversion);
    rectToPrint.Bottom = (int)(y1 * conversion);
    rectToPrint.Left = (int)(x * conversion);
    rectToPrint.Right = (int)(decimal)(x1 * conversion);

    //Calculate the size of the page
    RECT rectPage;
    rectPage.Top = (int)(y * conversion);
    rectPage.Bottom = (int)(y1 * conversion);
    rectPage.Left = (int)(x * conversion);
    rectPage.Right =  (int)(x1 * conversion);
            
    IntPtr hdc = g.GetHdc();

    FORMATRANGE fmtRange;
    fmtRange.chrg.cpMax = charTo;//Indicate character from to character to 
    fmtRange.chrg.cpMin = charFrom;
    fmtRange.hdc = hdc;          //Use the same DC for measuring & rendering
    fmtRange.hdcTarget = hdc;    //Point at printer hDC
    fmtRange.rc = rectToPrint;   //Indicate the area on page to print
    fmtRange.rcPage = rectPage;  //Indicate size of page
            

    IntPtr res = IntPtr.Zero;

    IntPtr wparam = IntPtr.Zero;
    wparam = new IntPtr(1);

    //Get the pointer to the FORMATRANGE structure in memory
    IntPtr lparam = IntPtr.Zero;
    lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
    Marshal.StructureToPtr(fmtRange, lparam, false);

    //Send the rendered data for printing 
    res = SendMessage(Handle, EM_FORMATRANGE, wparam, lparam);

    //Free the block of memory allocated
    Marshal.FreeCoTaskMem(lparam);

    //Release the device context handle obtained by a previous call
    g.ReleaseHdc(hdc);
            
    //Return last + 1 character printer
    return res.ToInt32();
}

After that, there was a problem. The background color of the control RichetextBox was also rendered, while I was interested rendering only the text. So I found this, which explains how to make a control transparent:

//START : make this control trasparent
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern IntPtr LoadLibrary(string lpFileName);

protected override CreateParams CreateParams
{
    get
    {
        CreateParams prams = base.CreateParams;
        if (LoadLibrary("msftedit.dll") != IntPtr.Zero)
        {
            prams.ExStyle |= 0x020; // transparent
            prams.ClassName = "RICHEDIT50W";
        }
           return prams;
    }
}
// END

Et voilà. At this point, we have all we need to render real RTF text on a graphic object. I called it BoxTesto; it is a box (rectangle) with a start and an end point and all of the properties derived from the Ele class. In addition, it has an RTF property that contains a string with the RTF text. When I draw a BoxTesto, I create a "transparent" RichTextBoxPrintCtrl control and assign RichTextBoxPrintCtrl.rtf with BoxTesto.rtf. Then I call RichTextBoxPrintCtrl.Draw.

Undo/Redo

I choose to implement the undo/redo as a double-linked list. Each item on the list contains a reference to the object of interest, an old copy of the object and a new copy of the object. In the next image, I present the undo/redo list in blue and the modified objects in black.

Screenshot - vectshapes6.jpg

Every time I change an object, I store an element in the undo/redo list. When I need to undo an action, I copy the OLD object properties over the interested object properties. Then I go back one element in the undo/redo list. When I need to redo an action, I go forward one element in the undo/redo list. Then I copy the NEW object properties over the object referenced by the element. The "lazy" way: in my old project, I implemented undo/redo like a circular array where the objects stored were the entire collection of graphic objects. That was an easy and fast solution, but too memory-hungry.

Drawing Images (Rotation/Transparency)

Screenshot - vectshapes8.jpg

Now I focus on image drawing (method Draw, class ImgBox, file Shapes.cs) to show how to implement image rotation and transparency:

//get the back color from the first pixel (UP-LEFT)
Color backColor = this.img.GetPixel(0, 0); 
//Create a tmp Bitmap and a graphic object
// the dimension of the tmp bitmap must permit the rotation of img
int dim = 
    (int)System.Math.Sqrt(img.Width * img.Width + img.Height * img.Height);
Bitmap curBitmap = new Bitmap(dim, dim);
Graphics curG = Graphics.FromImage(curBitmap);

if (this.Rotation >
 0)
{
    // activate the rotation on the graphic obj
    Matrix X = new Matrix();
    X.RotateAt(this.Rotation, new PointF(curBitmap.Width / 2, 
        curBitmap.Height / 2));
    curG.Transform = X;        
    X.Dispose();
}
// I draw img over the tmp bitmap 
curG.DrawImage(img, (dim - img.Width) / 2, (dim - img.Height) / 2, 
    img.Width, img.Height);

if (this.Trasparent)
    curBitmap.MakeTransparent(backColor); // here I perform trasparency

curG.Save();
// I draw the tmp bitmap on canvas
g.DrawImage(curBitmap, new Rectangle(this.X + dx, 
    this.Y + dy, this.X1 - this.X, this.Y1 - this.Y));
    
curG.Dispose();
curBitmap.Dispose();

History

  • 15 June, 2007 -- Original version posted

Update 1

  1. Added ZoomIN/ZoomOut
  2. Added right mouse click canvas movement
  3. Added image rotation
  4. Added Transparent Image property

Update 2

  1. Added obj --> Group and obj --> deGroup functions
  2. Added a rotation handle for object rotation
    Screenshot - vectshapes9.jpg
  3. Added File--> Save selected objects / File --> Load objects
  4. Created Help page

Update 3

  1. Added Gradient Line Fill Color, i.e. draw a shape, fill it and then set the UseGradientLineColor property to true
  2. Added Group Resize / Rotation and Zoom Management
  3. Can show a Group like a GraphicPath, i.e. select some Lines and then select them, group them and set the graphPath property to true
  4. Added example *.shape files. Load them from File --> Load
    Screenshot - vectshapes10.jpg

Update 4

  1. Added Free Hand Pen Tool
  2. Added Obj-->Poly--> X/Y Mirror
  3. Added Obj-->Poly--> Merge
  4. Added Obj-->Poly--> Extract Points

Update 5

  1. Added Draw Graph Tool

License

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

Share

About the Author

andrea contoli
Software Developer
Italy Italy
No Biography provided

Comments and Discussions

 
Questionhow about zoom operation? Pinmemberyxsylyh16-Oct-14 21:06 
QuestionHi! There is a bug PinmemberMember 101085112-Jul-14 9:42 
AnswerRe: Hi! There is a bug Pinmemberacontoli30-Jul-14 22:08 
Questionhow about draw an active gif , marching ant or running pipes PinmemberMarksion2-Apr-14 22:55 
QuestionTag property PinmemberTrial227-Aug-13 3:53 
AnswerRe: Tag property Pinmemberacontoli27-Aug-13 20:31 
add something like that:
 
[CategoryAttribute("TAG"), DescriptionAttribute("free text TAG")]
public string TAG
{
    set
    {
        _TAG = value;
    }
    get
    {
        return _TAG;
    }
}
 
in the
 
"public abstract class Ele"
dnk

GeneralRe: Tag property PinmemberTrial228-Aug-13 4:31 
QuestionGuideline or Magnetic effects... Pinmemberklrodel4-Jun-13 22:29 
GeneralMy vote of 5 Pinmemberavoliogab1-Feb-13 8:07 
Generalmy vote of 5 PinmemberMember 822540725-Nov-12 3:56 
GeneralMy vote of 5 PinmemberMember 822540725-Nov-12 3:54 
GeneralMy vote of 5 Pinmembernekimiro30-Jun-12 2:50 
QuestionExcellent ! PinmemberMember 81679031-Jun-12 4:42 
QuestionHow i can get line (or other) object inside the mousover method? PinmemberMember 864892327-Apr-12 7:58 
AnswerRe: How i can get line (or other) object inside the mousover method? Pinmemberacontoli1-May-12 20:35 
GeneralMy vote of 5 Pinmembermanoj kumar choubey21-Feb-12 23:43 
GeneralMy vote of 5 [modified] PinmemberNazmi Altun12-Oct-11 23:25 
GeneralImplementing Left,Center and Right Text allignment PinmemberMember 774292525-Jun-11 20:37 
GeneralSave /Save As /New PinmemberMember 774292525-Jun-11 19:10 
GeneralSave/Save As/New PinmemberMember 774292525-Jun-11 19:10 
GeneralMini SlideShow !! Pinmemberkienhv_8715-Jun-10 23:18 
GeneralRe: Mini SlideShow !! Pinmemberacontoli16-Jun-10 0:19 
GeneralRe: Mini SlideShow !! [modified] Pinmemberkienhv_8716-Jun-10 15:13 
QuestionWhat time can test the links? Pinmemberhudawei12-Apr-10 5:41 
AnswerRe: What time can test the links? Pinmemberacontoli14-Apr-10 21:14 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.141015.1 | Last Updated 9 Jun 2009
Article Copyright 2007 by andrea contoli
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid