Erase/delete parts of a GraphicsPath? - c#

How would I approach making an eraser for a Paint-type application, when my free drawing is done with lines stored in a GraphicsPath?
I've tried the following, but it looks like the Points IEnumerable is read-only. The tricky part is that I have to keep a thin border around the painted strokes so the eraser would have to maintain the border around the remaining GraphicsPath.
Here's what I tried to remove part of the GraphicsPath:
private void Testform_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
List <PointF> list = _drawingPath.PathData.Points.ToList<PointF>();
for (int i = 0; i < 50; i++)
{
list.RemoveAt(i);
}
Invalidate();
}
}
That bit is just for testing hence no exception handling and the arbitrary use of 50. If you scribble a bit on the form you'll end up with more than 50 points to test.
And here is my complete code:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
namespace Cartographer
{
public partial class testform : Form
{
private GraphicsPath _drawingPath = new GraphicsPath();
private Point lastMouseLocation;
private bool drawing = false;
public testform()
{
InitializeComponent();
}
private void testform_Load(object sender, EventArgs e)
{
this.Paint += Testform_Paint;
this.MouseMove += Testform_MouseMove;
this.MouseDown += Testform_MouseDown;
this.DoubleBuffered = true;
}
private void Testform_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
List <PointF> list = _drawingPath.PathData.Points.ToList<PointF>();
for (int i = 0; i < 50; i++)
{
list.RemoveAt(i);
}
Invalidate();
}
}
private void Testform_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
drawing = true;
_drawingPath.AddLine(lastMouseLocation, e.Location);
Invalidate();
}
if (e.Button == MouseButtons.None && drawing)
{
drawing = false;
_drawingPath.StartFigure();
}
lastMouseLocation = e.Location;
}
private void Testform_Paint(object sender, PaintEventArgs e)
{
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (SolidBrush b = new SolidBrush(Color.Blue))
using (Pen p = new Pen(b, 51))
{
p.StartCap = System.Drawing.Drawing2D.LineCap.Round;
p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
e.Graphics.DrawPath(p, _drawingPath);
}
using (SolidBrush b = new SolidBrush(Color.LightGreen))
using (Pen p = new Pen(b, 50))
{
p.StartCap = System.Drawing.Drawing2D.LineCap.Round;
p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
e.Graphics.DrawPath(p, _drawingPath);
}
}
}
}
If you try it for yourself you'll notice the border I need to maintain. All I need to figure out is how to erase parts of the drawing while maintaining the border.
Thank you!

Related

C# Graphics class want to make signature panel : input from drawing tablet

i am trying to make signature panel in c# windowsform application where input is from drawing tablet
my code as below this code working for line drawing not dot created.
So please suggest how dot and line both are create.
{
Graphics graphics;
Boolean cusorMoving = false;
Pen cursorPen;
int cursorX = -1;
int cursorY = -1;
public SignPad()
{
InitializeComponent();
graphics = panel2.CreateGraphics();
cursorPen = new Pen(Color.Black, 2);
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
cursorPen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
cursorPen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
}
Mouse Down event
private void panel2_MouseDown(object sender, MouseEventArgs e)
{
cusorMoving = true;
cursorX = e.X;
cursorY = e.Y;
}
private void panel2_MouseUp(object sender, MouseEventArgs e)
{
cusorMoving = false;
cursorX = -1;
cursorY = -1;
}
Mouse Move event
private void panel2_MouseMove(object sender, MouseEventArgs e)
{
if (cursorX != -1 && cursorY != -1 && cusorMoving == true)
{
graphics.DrawLine(cursorPen, new Point(cursorX, cursorY), e.Location);
cursorX = e.X;
cursorY = e.Y;
}
}
You need to store individual points in a collection and draw them separately in the Paint handler. Every time you add a point to the collection, you also need to tell the panel to draw the area where the new segment was added. Something like this:
using System.Collections.Generic;
using System.Drawing;
namespace Lines
{
public partial class SignPad : Form
{
Pen cursorPen = SystemPens.ControlText;
List<Point> points = new List<Point>();
bool cursorMoving = false;
public SignPad()
{
InitializeComponent();
cursorPen = new Pen(Color.Black, 2);
cursorPen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
cursorPen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
}
private void panel2_Paint(object? sender, PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
for (int i = 1; i < points.Count; ++i)
g.DrawLine(cursorPen, points[i - 1], points[i]);
}
private void panel2_MouseDown(object? sender, MouseEventArgs e)
{
if (!cursorMoving)
{
cursorMoving = true;
points.Clear();
points.Add(e.Location);
panel2.Invalidate();
}
}
private void panel2_MouseMove(object? sender, MouseEventArgs e)
{
if (cursorMoving && points.Count > 0)
{
var p = e.Location;
var q = points[points.Count - 1];
var r = Rectangle.FromLTRB(Math.Min(p.X, q.X), Math.Min(p.Y, q.Y), Math.Max(p.X, q.X), Math.Max(p.Y, q.Y));
r = Rectangle.Inflate(r, (int)cursorPen.Width, (int)cursorPen.Width);
points.Add(p);
panel2.Invalidate(r);
}
}
private void panel2_MouseUp(object? sender, MouseEventArgs e)
{
cursorMoving = false;
}
}
}
Don't forget to add the Paint handler the same way you added MouseMove, MouseDown and MouseUp handlers - in the Designer.

The image is supposed to be moved inside the PictureBox via MouseMove events

Before starting a project, I want to know beforehand whether the following would work.
The application creates a System.Drawing.Bitmap and paints on it.
The Bitmap is much larger than the PictureBox. The Height of the Bitmap and the PictureBox are the same. Now I would like to be able to move the Image from left to right within (along) the PictureBox by pressing the left mouse button and moving it.
See the drawing:
I just realized that I haven't answered yet, which I am trying to make up for.
As a solution, I use Jimi's solution. I've shortened the code here to meet my needs.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
namespace Zoom_an_image_from_the_mouse_location
{
public partial class FormMain : Form
{
public FormMain()
{
InitializeComponent();
string imagePath = "C:\\Users\\yourPath\\Pictures\\....jpeg";
drawingImage = (Bitmap)Image.FromStream(new MemoryStream(File.ReadAllBytes(imagePath)));
imageRect = new RectangleF(Point.Empty, drawingImage.Size);
canvas = new PictureBoxEx(new Size(525,700));
canvas.Location = new Point(10, 10);
canvas.MouseMove += this.canvas_MouseMove;
canvas.MouseDown += this.canvas_MouseDown;
canvas.MouseUp += this.canvas_MouseUp;
canvas.Paint += this.canvas_Paint;
this.Controls.Add(canvas);
}
private void FormMain_Load(object sender, EventArgs e)
{
this.BackColor = Color.FromArgb(174, 184, 177);
}
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
}
private float rotationAngle = 0.0f;
private float zoomFactor = 1.0f;
private RectangleF imageRect = RectangleF.Empty;
private PointF imageLocation = PointF.Empty;
private PointF mouseLocation = PointF.Empty;
private Bitmap drawingImage = null;
private PictureBoxEx canvas = null;
private void canvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
mouseLocation = e.Location;
imageLocation = imageRect.Location;
canvas.Cursor = Cursors.NoMove2D;
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
imageRect.Location =
new PointF(imageLocation.X + (e.Location.X - mouseLocation.X),
imageLocation.Y); //+ (e.Location.Y - mouseLocation.Y));
canvas.Invalidate();
}
private void canvas_MouseUp(object sender, MouseEventArgs e) =>
canvas.Cursor = Cursors.Default;
private void canvas_Paint(object sender, PaintEventArgs e)
{
var drawingRect = GetDrawingImageRect(imageRect);
using (var mxRotation = new Matrix())
using (var mxTransform = new Matrix())
{
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
mxRotation.RotateAt(rotationAngle, GetDrawingImageCenterPoint(drawingRect));
mxTransform.Multiply(mxRotation);
e.Graphics.Transform = mxTransform;
e.Graphics.DrawImage(drawingImage, drawingRect);
}
}
#region Drawing Methods
public RectangleF GetScaledRect(RectangleF rect, float scaleFactor) =>
new RectangleF(rect.Location,
new SizeF(rect.Width * scaleFactor, rect.Height * scaleFactor));
public RectangleF GetDrawingImageRect(RectangleF rect) =>
GetScaledRect(rect, zoomFactor);
public PointF GetDrawingImageCenterPoint(RectangleF rect) =>
new PointF(rect.X + rect.Width / 2f, rect.Y + rect.Height / 2f);
#endregion
}
}
[DesignerCategory("Code")]
public class PictureBoxEx : PictureBox
{
public PictureBoxEx() : this(new Size(525, 700)) { }
public PictureBoxEx(Size size)
{
SetStyle(ControlStyles.Selectable | ControlStyles.UserMouse, true);
this.BorderStyle = BorderStyle.FixedSingle;
this.Size = size;
}
}

What is best way to write in windows forms with pen-tablet like wacom intuos in C#

i have a problem.
I'm writing a program that writes on it with a stylus.
First, i create a windows form with a panel.
second, this code:
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
using System.Diagnostics;
using System.Drawing.Drawing2D;
namespace testWrite
{
public partial class Form1 : Form
{
Graphics g;
int x = -1;
int y = -1;
bool moving = false;
Pen pen;
public Form1()
{
InitializeComponent();
g = panel1.CreateGraphics();
pen = new Pen(Color.Black, 5);
pen.SetLineCap(System.Drawing.Drawing2D.LineCap.Round, System.Drawing.Drawing2D.LineCap.Round, System.Drawing.Drawing2D.DashCap.Round);
pen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
pen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
g.DrawLine(pen, new Point(x, y), e.Location);
x = e.X;
y = e.Y;
}
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
x = -1;
y = -1;
moving = false;
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
x = e.X;
y = e.Y;
moving = true;
}
}
}
I use this app with a Wacom intuos
But the result is not so good because a few words are lost...haizzz
toi tên la trần
quang hieu
hello heloo
especially, when i write fast or the text is small.
when i write in Microsoft Paint, it is very good
What is best way to to write in windows forms with pen-tablet like wacom intuos?
UPDATE 1:
With cmt from TaW.
Thanks for your help. But, that's not what I need...
i was change my code to:
public partial class Form1 : Form
{
List<Point> curPoints = new List<Point>();
List<List<Point>> allPoints = new List<List<Point>>();
public Form1()
{
InitializeComponent();
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
// here we should check if the distance is more than a minimum!
curPoints.Add(e.Location);
// let it show
panel1.Invalidate();
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
if (curPoints.Count > 1)
{
// ToList creates a copy
allPoints.Add(curPoints.ToList());
curPoints.Clear();
}
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
if (curPoints.Count > 1)
{
// begin fresh line or curve
curPoints.Clear();
// startpoint
curPoints.Add(e.Location);
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
// here you can use DrawLines or DrawCurve
// current line
if (curPoints.Count > 1) e.Graphics.DrawCurve(Pens.Red, curPoints.ToArray());
// other lines or curves
foreach (List<Point> points in allPoints)
if (points.Count > 1) e.Graphics.DrawCurve(Pens.Red, points.ToArray());
}
}
But nothing better. The result is worse...
I tried to write: "Hello my name is Hieu", but is not run...
Looks like a pen-tablet differs from a mouse when use to write. Because, with mouse i feel that is better in this code...
UPDATE 2:
With code by Idle_Mind. It will be fine if i set pen-tablet:
With setting "Click", it is not OK
How to fix it, i don't want to set "Double Click" to my pen !
Here's my version...worked great for me. You might need to adjust your tablet settings so that it picks up everything correctly:
public partial class FormTablet : Form
{
private Point lastPoint;
private GraphicsPath GP = null;
private List<GraphicsPath> GPs = new List<GraphicsPath>();
public FormTablet()
{
InitializeComponent();
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
pictureBox1.Invalidate();
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
lastPoint = new Point(e.X, e.Y);
GP = new GraphicsPath();
GP.AddLine(lastPoint, lastPoint);
GPs.Add(GP);
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Point pt = new Point(e.X, e.Y);
GP.AddLine(lastPoint, pt);
lastPoint = pt;
pictureBox1.Invalidate();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
GP = null;
pictureBox1.Invalidate();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (checkBox1.Checked)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
}
using(Pen p = new Pen(Color.Black, (int)numericUpDown1.Value))
{
p.LineJoin = LineJoin.Round;
p.MiterLimit = p.Width / 2;
foreach (GraphicsPath path in GPs)
{
if (path.PathPoints.Count() > 2)
{
// draw the path
e.Graphics.DrawPath(p, path);
}
else
{
// just draw a single dot
Rectangle rc = new Rectangle(Point.Round(path.PathPoints[0]), new Size(1, 1));
rc.Inflate((int)numericUpDown1.Value, (int)numericUpDown1.Value);
e.Graphics.FillEllipse(Brushes.Black, rc);
}
}
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
pictureBox1.Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
GPs.Clear();
pictureBox1.Invalidate();
}
}

Eraser tool on drawing while keeping border

I'm making a 2D map drawing tool and have a problem. The border around the painted "land" acts as a "coast". When the user paints in more water, the land should be removed/replaced by the water brush, but the coasts should adapt with the change (because coasts are drawn in real-time).
Here is my current code:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
namespace Cartographer
{
public partial class testform : Form
{
private GraphicsPath _drawingPath = new GraphicsPath();
private Point lastMouseLocation;
private bool drawing = false;
public testform()
{
InitializeComponent();
}
private void testform_Load(object sender, EventArgs e)
{
this.Paint += Testform_Paint;
this.MouseMove += Testform_MouseMove;
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.Opaque, false);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
private void Testform_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
drawing = true;
_drawingPath.AddLine(lastMouseLocation, e.Location);
//_drawingPath.AddEllipse(new Rectangle(e.Location, new Size(10, 10)));
Invalidate();
}
if (e.Button == MouseButtons.None && drawing)
{
drawing = false;
_drawingPath.StartFigure();
}
lastMouseLocation = e.Location;
}
private void Testform_Paint(object sender, PaintEventArgs e)
{
//e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
//e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
//e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
using (SolidBrush b = new SolidBrush(Color.FromArgb(100, Color.Brown)))
using (Pen p = new Pen(b, 55))
{
p.StartCap = System.Drawing.Drawing2D.LineCap.Round;
p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
e.Graphics.DrawPath(p, _drawingPath);
}
using (SolidBrush b = new SolidBrush(Color.Brown))
using (Pen p = new Pen(b, 50))
{
p.StartCap = System.Drawing.Drawing2D.LineCap.Round;
p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
e.Graphics.DrawPath(p, _drawingPath);
}
}
private void btnErase_Click(object sender, EventArgs e)
{
List<PointF> ptsList = new List<PointF>();
for (int i = 0; i < 20; i++)
{
ptsList.Add(_drawingPath.PathData.Points[i]);
}
_drawingPath = ErasePointsFromPath(_drawingPath, ptsList.ToArray<PointF>());
this.Invalidate();
}
private GraphicsPath ErasePointsFromPath(GraphicsPath path, PointF[] toRemove)
{
GraphicsPath ret = new GraphicsPath(
path.PathData.Points.Skip(20).ToArray(),
path.PathData.Types.Skip(20).ToArray());
return ret;
}
}
}
I want my software to have borders like this, even when the water is brushed over land:
Right now it looks like this (which is fine, it's for testing) but I have no way to fine-tune that little river/lake in the middle of the land mass because the coasts will disappear if I simply paint over it.
I have another working version of this that works with GdipWindingModeOutlineso if there's a way to do this with that I can adapt.
For the record I've tried erasing points from the GraphicsPath but that erases huge chunks instead of the simple "eraser tool" type of functionality I'm aiming for.
Thank you!

Why can't I paint a rectangle with this code in pictureBox?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace UTUResultWithCoordinates
{
public partial class GetCoordinates : Form
{
private string sem;
private string branch;
private int mouseisdown = 0;
private int recx = 0;
private int recy = 0;
private int mousemovingwhilepressed = 0;
public GetCoordinates()
{
InitializeComponent();
}
public GetCoordinates(string p, string p_2)
{
// TODO: Complete member initialization
InitializeComponent();
branch = p;
sem = p_2;
pictureBox1.Controls.Add(pictureBox2);
pictureBox2.Location = new Point(0, 0);
pictureBox2.BackColor = Color.Transparent;
pictureBox2.Width = 1191;
pictureBox2.Height = 842;
}
private void GetCoordinates_Load(object sender, EventArgs e)
{
pictureBox1.ImageLocation = #"D:\DotNet\UTUResultWithCoordinates\UTUResultWithCoordinates\bin\Debug\ComputerScience6.jpg";
}
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
if (mouseisdown == 1 && mousemovingwhilepressed==1)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = this.CreateGraphics();
Pen myPen = new Pen(System.Drawing.Color.Blue, 100);
Rectangle myRectangle = new Rectangle(recx, recy, 20, 20);
e.Graphics.DrawRectangle(myPen, myRectangle);
}
}
private void pictureBox2_MouseDown(object sender, MouseEventArgs e)
{
mouseisdown = 1;
recx = e.X;
recy = e.Y;
pictureBox2.CreateGraphics();
}
private void pictureBox2_MouseMove(object sender, MouseEventArgs e)
{
label1.Text = e.X + "," + e.Y;
mousemovingwhilepressed = 1;
recx = e.X;
recy = e.Y;
pictureBox2.CreateGraphics();
}
private void pictureBox2_MouseUp(object sender, MouseEventArgs e)
{
mousemovingwhilepressed = 0;
mouseisdown = 0;
pictureBox2.CreateGraphics();
}
}
}
I have created a pictureBox1 in which an image is displayed. Then I have created a pictureBox2 inside it so that I can paint on that image a rectangle by dragging the mouse. But nothing is happening on clicking the mouse. What is the error?
Calling CreateGraphics does not trigger the painting of the PictureBox.
Use Invalidate to cause a redraw.
For a full example see: How to select an area on a PictureBox.Image with mouse in C#
Side notes:
Calling InitializeControl in a method other than the constructor is not a good idea.
when you need a boolean use a boolean, not an integer.
Objects that implement IDisposable (such as Pen) should be created as few times as possible and be disposed when no longer needed/used.

Categories

Resources