Monday, January 21, 2019

How to manage different layers in drawing?

How drawing and merging.


The problem is quite trivial and, in this article, I’m going to explain how we can do this.

The first step is to create a Form where to locate a panel for drawing. 


The example will consist in three layers, each of these with an assigned name and fore-color.

It should be possible to draw lines, with mouse use, on different layers, each properly selected.

The combobox contains a list of layers and allows user to enable the current one, while the checkbox allows to enable the layer visibility.

Layer Definition

I have imagined that a layer has three simple properties: Name, Canvas, and a Visible.

public class DGLayerItem
{
        public string Name { get; set; }
        public Bitmap Canvas { get; set; }
        public bool Visible { get; set; } = true;

        public Color ForeColor { get; set; } = Color.Black;

        public DGLayerItem(string name, Bitmap canvas, Color forecolor)
        {
            Name = name;
            Canvas = canvas;
            ForeColor = forecolor;
        }
}



The above class represents a layer.

Then, I defined a layer list on a Form:

 
List<dglayeritem> _layers = new List<dglayeritem>();

For practical reason this is a list of three layer

_layers.Add(new DGLayerItem("Layer 1", new Bitmap(panCanvas.Width, panCanvas.Height), Color.Green));
_layers.Add(new DGLayerItem("Layer 2", new Bitmap(panCanvas.Width, panCanvas.Height), Color.Blue));
_layers.Add(new DGLayerItem("Layer 3", new Bitmap(panCanvas.Width, panCanvas.Height), Color.Red));


Each Canvas has the panel size.


This is quite all we need in terms of logic; the next step is how to draw and showing the miracle.

Drawing

The key point for drawing is to manage the mouse events on a panel. For example, we need to intercept the click on the area and the mouse moving.
For that reason, we have to manage three events:

  •         MouseDown: initialize drawing.
  •         MouseMove: manage drawing.
  •         MouseUp: terminate drawing phase.


MousDown

private void panCanvas_MouseDown(object sender, MouseEventArgs e)
{

if (e.Button == MouseButtons.Left)
       {
_prevPoint = e.Location;

              string selectedLayerName = mbLayers.Items[cmbLayers.SelectedIndex].ToString();
              _layer = _layers.FirstOrDefault(i => i.Name == selectedLayerName);
               
}
}


Current point

_prevPoint = e.Location;

Current Layer

string selectedLayerName = cmbLayers.Items[cmbLayers.SelectedIndex].ToString();
_layer = _layers.FirstOrDefault(i => i.Name == selectedLayerName);



MouseMove

private void panCanvas_MouseMove(object sender, MouseEventArgs e)
{

if (e.Button == MouseButtons.Left)
       {
             int mouseX = e.X;
             int mouseY = e.Y;

              using (Graphics g1 = panCanvas.CreateGraphics())
              {
                    using (Graphics g2 = Graphics.FromImage(_layer.Canvas))
                    {
                           g1.Line(_prevPoint, e.Location, _layer.ForeColor);
                           g2.Line(_prevPoint, e.Location, _layer.ForeColor);
                    }
}
              _prevPoint = e.Location;             
}
}

Layer not selected

if (_layer == null) return;


Get instruments


using (Graphics g1 = panCanvas.CreateGraphics())


In order to draw with GDI+ we have to get a Graphics object. 

This object it is a fundamental part in every drawing phase. Every bitmap owns a graphic object.

We have to ask the object, use it, and dispose it. The using keyword guarantee creation and destruction.


In the procedure we draw in two different places! g1 and g2:


The layer canvas

g2.Line(_prevPoint, e.Location, _layer.ForeColor);

And the panel canvas

g1.Line(_prevPoint, e.Location, _layer.ForeColor);



Why drawing in two areas?

It is a trick in order to avoid loss of time and obtain a good quality result.

It should be possible to draw only in the layer and the merge every think on the panel, but with problems of flickering or line width increasing.

MouseUp


private void panCanvas_MouseUp(object sender, MouseEventArgs e)
{
if (_status == DrawStatus.Drawing)
       {
             RepaintLayers(true);
}
}


The final part is resumed on a RepaintLayer() procedure:


private void RepaintLayers(bool clearCanvas = false)
{
Rectangle rect = new Rectangle(panCanvas.Location, panCanvas.Size);

       using (Graphics g = panCanvas.CreateGraphics())
       {
             if (clearCanvas)
                    g.Clear(panCanvas.BackColor);

foreach (var item in _layers)
              {
                    if (item.Visible)
                           g.DrawImage(item.Canvas, 0, 0);
}
}
}

The core part of this code is:

foreach (var item in _layers)
{
       if (item.Visible)
             g.DrawImage(item.Canvas, 0, 0);
}


Every visible layer is merged on the panel bitmap.