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.