using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; using System.Runtime.Serialization; using System.Windows.Forms; namespace CraftSynth.ImageEditor { /// /// Line graphic object /// //[Serializable] public class DrawLine : DrawObject { private Point startPoint; private Point endPoint; private const string entryStart = "Start"; private const string entryEnd = "End"; /// /// Graphic objects for hit test /// private GraphicsPath areaPath = null; private Pen areaPen = null; private Region areaRegion = null; private bool _disposed = false; public DrawLine() { startPoint.X = 0; startPoint.Y = 0; endPoint.X = 1; endPoint.Y = 1; ZOrder = 0; Initialize(); } #region Destruction protected override void Dispose(bool disposing) { if (!this._disposed) { if (disposing) { // Free any managed objects here. if (this.areaPath!=null) { this.areaPath.Dispose(); } if (this.areaPen!=null) { this.areaPen.Dispose(); } if (this.areaRegion!=null) { this.areaRegion.Dispose(); } } // Free any unmanaged objects here. this._disposed = true; } base.Dispose(disposing); } ~DrawLine() { this.Dispose(false); } #endregion public DrawLine(int x1, int y1, int x2, int y2, Color lineColor, int lineWidth, DrawingPens.PenType penType, LineCap endCap) { startPoint.X = x1; startPoint.Y = y1; endPoint.X = x2; endPoint.Y = y2; Color = lineColor; PenWidth = lineWidth; PenType = penType; EndCap = endCap; ZOrder = 0; TipText = String.Format("Line Start @ {0}-{1}, End @ {2}-{3}", x1, y1, x2, y2); Initialize(); } public override void Draw(Graphics g) { g.SmoothingMode = SmoothingMode.AntiAlias; Pen pen; if (DrawPen == null) { pen = new Pen(Color, PenWidth); DrawingPens.SetCurrentPen(ref pen, PenType, EndCap); } else pen = (Pen) DrawPen.Clone(); GraphicsPath gp = new GraphicsPath(); gp.AddLine(startPoint, endPoint); // Rotate the path about it's center if necessary if (Rotation != 0) { RectangleF pathBounds = gp.GetBounds(); Matrix m = new Matrix(); m.RotateAt(Rotation, new PointF(pathBounds.Left + (pathBounds.Width / 2), pathBounds.Top + (pathBounds.Height / 2)), MatrixOrder.Append); gp.Transform(m); } g.DrawPath(pen, gp); gp.Dispose(); pen.Dispose(); } /// /// Clone this instance /// public override DrawObject Clone() { DrawLine drawLine = new DrawLine(); drawLine.startPoint = startPoint; drawLine.endPoint = endPoint; FillDrawObjectFields(drawLine); return drawLine; } public override int HandleCount { get { return 2; } } /// /// Get handle point by 1-based number /// /// /// public override Point GetHandle(int handleNumber) { GraphicsPath gp = new GraphicsPath(); Matrix m = new Matrix(); gp.AddLine(startPoint, endPoint); RectangleF pathBounds = gp.GetBounds(); m.RotateAt(Rotation, new PointF(pathBounds.Left + (pathBounds.Width / 2), pathBounds.Top + (pathBounds.Height / 2)), MatrixOrder.Append); gp.Transform(m); Point start, end; start = Point.Truncate(gp.PathPoints[0]); end = Point.Truncate(gp.PathPoints[1]); gp.Dispose(); m.Dispose(); if (handleNumber == 1) return start; else return end; } /// /// Hit test. /// Return value: -1 - no hit /// 0 - hit anywhere /// > 1 - handle number /// /// /// public override int HitTest(Point point) { if (Selected) for (int i = 1; i <= HandleCount; i++) { GraphicsPath gp = new GraphicsPath(); gp.AddRectangle(GetHandleRectangle(i)); bool vis = gp.IsVisible(point); gp.Dispose(); if (vis) return i; } // OK, so the point is not on a selection handle, is it anywhere else on the line? if (PointInObject(point)) return 0; return -1; } protected override bool PointInObject(Point point) { CreateObjects(); //return AreaPath.IsVisible(point); return AreaRegion.IsVisible(point); } public override Rectangle GetBounds(Graphics g) { var rectF = areaRegion.GetBounds(g); rectF = GetNormalizedRectangle(rectF); var rect = new Rectangle((int)Math.Floor(rectF.X), (int)Math.Floor(rectF.Y), (int)Math.Ceiling(rectF.Width), (int)Math.Ceiling(rectF.Height)); return rect; } public override bool IntersectsWith(Rectangle rectangle) { CreateObjects(); return AreaRegion.IsVisible(rectangle); } public override Cursor GetHandleCursor(int handleNumber) { switch (handleNumber) { case 1: case 2: return Cursors.SizeAll; default: return Cursors.Default; } } public override void MoveHandleTo(Point point, int handleNumber) { //GraphicsPath gp = new GraphicsPath(); //Matrix m = new Matrix(); //if (handleNumber == 1) // gp.AddLine(point, endPoint); //else // gp.AddLine(startPoint, point); //RectangleF pathBounds = gp.GetBounds(); //m.RotateAt(Rotation, new PointF(pathBounds.Left + (pathBounds.Width / 2), pathBounds.Top + (pathBounds.Height / 2)), MatrixOrder.Append); //gp.Transform(m); //Point start, end; //start = Point.Truncate(gp.PathPoints[0]); //end = Point.Truncate(gp.PathPoints[1]); //gp.Dispose(); //m.Dispose(); //if (handleNumber == 1) // startPoint = start; //else // endPoint = end; if (handleNumber == 1) startPoint = point; else endPoint = point; Dirty = true; Invalidate(); } public override void Move(int deltaX, int deltaY) { startPoint.X += deltaX; startPoint.Y += deltaY; endPoint.X += deltaX; endPoint.Y += deltaY; Dirty = true; Invalidate(); } public override void SaveToStream(SerializationInfo info, int orderNumber, int objectIndex) { info.AddValue( String.Format(CultureInfo.InvariantCulture, "{0}{1}-{2}", entryStart, orderNumber, objectIndex), startPoint); info.AddValue( String.Format(CultureInfo.InvariantCulture, "{0}{1}-{2}", entryEnd, orderNumber, objectIndex), endPoint); base.SaveToStream(info, orderNumber, objectIndex); } public override void LoadFromStream(SerializationInfo info, int orderNumber, int objectIndex) { startPoint = (Point)info.GetValue( String.Format(CultureInfo.InvariantCulture, "{0}{1}-{2}", entryStart, orderNumber, objectIndex), typeof(Point)); endPoint = (Point)info.GetValue( String.Format(CultureInfo.InvariantCulture, "{0}{1}-{2}", entryEnd, orderNumber, objectIndex), typeof(Point)); base.LoadFromStream(info, orderNumber, objectIndex); } /// /// Invalidate object. /// When object is invalidated, path used for hit test /// is released and should be created again. /// protected void Invalidate() { if (AreaPath != null) { AreaPath.Dispose(); AreaPath = null; } if (AreaPen != null) { AreaPen.Dispose(); AreaPen = null; } if (AreaRegion != null) { AreaRegion.Dispose(); AreaRegion = null; } } /// /// Create graphic objects used for hit test. /// protected virtual void CreateObjects() { if (AreaPath != null) return; // Create path which contains wide line // for easy mouse selection AreaPath = new GraphicsPath(); // Take into account the width of the pen used to draw the actual object AreaPen = new Pen(Color.Black, PenWidth < 7 ? 7 : PenWidth); // Prevent Out of Memory crash when startPoint == endPoint if (startPoint.Equals((Point)endPoint)) { endPoint.X++; endPoint.Y++; } AreaPath.AddLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y); AreaPath.Widen(AreaPen); // Rotate the path about it's center if necessary if (Rotation != 0) { RectangleF pathBounds = AreaPath.GetBounds(); Matrix m = new Matrix(); m.RotateAt(Rotation, new PointF(pathBounds.Left + (pathBounds.Width / 2), pathBounds.Top + (pathBounds.Height / 2)), MatrixOrder.Append); AreaPath.Transform(m); m.Dispose(); } // Create region from the path AreaRegion = new Region(AreaPath); } protected GraphicsPath AreaPath { get { return areaPath; } set { areaPath = value; } } protected Pen AreaPen { get { return areaPen; } set { areaPen = value; } } protected Region AreaRegion { get { return areaRegion; } set { areaRegion = value; } } #region Helper gunctions public static RectangleF GetNormalizedRectangle(float x1, float y1, float x2, float y2) { if (x2 < x1) { float tmp = x2; x2 = x1; x1 = tmp; } if (y2 < y1) { float tmp = y2; y2 = y1; y1 = tmp; } return new RectangleF(x1, y1, x2 - x1, y2 - y1); } public static RectangleF GetNormalizedRectangle(PointF p1, PointF p2) { return GetNormalizedRectangle(p1.X, p1.Y, p2.X, p2.Y); } public static RectangleF GetNormalizedRectangle(RectangleF r) { return GetNormalizedRectangle(r.X, r.Y, r.X + r.Width, r.Y + r.Height); } #endregion } }