using System.Drawing; using System.Drawing.Drawing2D; using Hawkeye.VisionBuilder.Workflow; using Hawkeye.VisionBuilder.Workflow.Datatypes; using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements; using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin; using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Poly; using OpenCvSharp; using OpenCvSharp.Extensions; using Point = System.Drawing.Point; using PointF = System.Drawing.PointF; using Matrix = Hawkeye.VisionBuilder.Workflow.Matrix; using Vector2 = Hawkeye.VisionBuilder.Workflow.Vector2; namespace Hawkeye.VisionBuilder.Features.ImagePreview { public partial class ImagePreviewControl : UserControl,ICanvas { private Bitmap _backBuffer; public List GraphicsElements { get; set; }=new List(); public EControlMode Mode { get; set; } = EControlMode.Preview; public Hawkeye.VisionBuilder.Workflow.Datatypes.HawkeyeImage Image { get => _image; set { var needsCentering = _image == null; _image = value; if(needsCentering) CenterImage(); } } public void CenterImage() { TransformationMatrix= Workflow.Matrix.Identity; var hRatio = Width/ (float)_image.ImageData.Width; var vRatio = Height / (float) _image.ImageData.Height; TransformationMatrix*=Matrix.CreateScale(Math.Min(hRatio,vRatio)); var shift = new Vector2(Width,Height)- Vector2.Transform(new Vector2(_image.ImageData.Width, _image.ImageData.Height), TransformationMatrix); TransformationMatrix *= Matrix.CreateTranslation(shift.X / 2f, shift.Y / 2f, 0); } public ImagePreviewControl() { InitializeComponent(); _backBuffer = new Bitmap(Width, Height); MouseWheel += ZoomPictureBoxWheel_MouseWheel; DoubleClick += ZoomPictureBoxWheel_DoubleClick; DoubleBuffered = true; SetStyle(ControlStyles.OptimizedDoubleBuffer, true); } private void ZoomPictureBoxWheel_DoubleClick(object sender, EventArgs e) { Invalidate(); } private float _zoom = 1; private void ZoomPictureBoxWheel_MouseWheel(object sender, MouseEventArgs e) { _zoom *= (e.Delta > 0) ? 0.9f : (1f / 0.9f); TransformationMatrix = TransformationMatrix * Matrix.CreateTranslation(-_lastMousePosition.X, -_lastMousePosition.Y, 0) * Matrix.CreateScale((e.Delta > 0) ? 0.9f : (1f / 0.9f)) * Matrix.CreateTranslation(_lastMousePosition.X, _lastMousePosition.Y, 0); Invalidate(); } public void FitToScreen() { if (Image == null) return; var width = Image.ImageData.Width; var height = Image.ImageData.Height; var aspectRatio = (float)width / height; var screenAspectRatio = (float)Width / Height; if (aspectRatio > screenAspectRatio) { _zoom = Width / (float)width; TransformationMatrix = Matrix.Identity; TransformationMatrix *= Matrix.CreateScale(_zoom); var shift = new Vector2(Width, Height) - Vector2.Transform(new Vector2(width, height), TransformationMatrix); TransformationMatrix *= Matrix.CreateTranslation(shift.X / 2f, shift.Y / 2f, 0); } else { _zoom = Height / (float)height; TransformationMatrix = Matrix.Identity; TransformationMatrix *= Matrix.CreateScale(_zoom); var shift = new Vector2(Width, Height) - Vector2.Transform(new Vector2(width, height), TransformationMatrix); TransformationMatrix *= Matrix.CreateTranslation(shift.X / 2f, shift.Y / 2f, 0); } Invalidate(); } private MouseButtons _mouseButton = MouseButtons.None; public Matrix TransformationMatrix { get; set; }=Matrix.Identity; private Vector2 _lastMousePosition; private const float NODE_SIZE = 10; protected override void OnMouseDown(MouseEventArgs e) { _mouseButton = e.Button; _lastMousePosition = new Vector2(e.X, e.Y); var threshold = NODE_SIZE / _zoom ; var inverted = Matrix.Invert(TransformationMatrix); var imageCoords = Vector2.Transform(new Vector2(e.X, e.Y), inverted); _selectedNode=GraphicsElements.Where(x=>x.Editable).Select(x => x.GetNode(imageCoords, threshold)).FirstOrDefault(x => x != null); } protected override void OnMouseUp(MouseEventArgs e) { _mouseButton = MouseButtons.None; } ToolTip _toolTip = new ToolTip(); static bool RectangleContainsPoint(int x, int y, int width, int height, int px, int py) { return px >= x && px <= x + width && py >= y && py <= y + height; } protected override void OnMouseLeave(EventArgs e) { _toolTip.Hide(this); } string GetContourInfo(Mat image, int x, int y) { var contours = image.FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple); var contour = contours.FirstOrDefault(c => Cv2.PointPolygonTest(c, new Point2f(x, y), false) >= 0); if (contour == null) return "No contour"; var area = Cv2.ContourArea(contour); var rect = Cv2.BoundingRect(contour); var width = rect.Width; var height = rect.Height; return $"Area: {area}, Width: {width}, Height: {height}"; } protected override void OnMouseMove(MouseEventArgs e) { // if Ctrl is pressed if ((ModifierKeys & Keys.Control) != 0&&_mouseButton == MouseButtons.None) { if(Image==null||Image.ImageData==null) return; var inverted = Matrix.Invert(TransformationMatrix); var imageCoords = Vector2.Transform(new Vector2(e.X, e.Y), inverted); if(!RectangleContainsPoint(0,0,Image.ImageData.Width,Image.ImageData.Height,(int)imageCoords.X,(int)imageCoords.Y)) return; if (Image.ImageData.Type() == MatType.CV_8UC3) { var pixel = Image.ImageData.Get((int)imageCoords.Y,(int)imageCoords.X); _toolTip.Show($"R:{pixel.Item0} G:{pixel.Item1} B:{pixel.Item2}", this, e.X + 15, e.Y + 15, 5000); return; } if (Image.ImageData.Type() == MatType.CV_8UC1) { var pixel = Image.ImageData.Get((int)imageCoords.Y,(int)imageCoords.X); var info= GetContourInfo(Image.ImageData, (int)imageCoords.X, (int)imageCoords.Y); _toolTip.Show($"Gray:{pixel}\n"+info, this, e.X+15, e.Y+15,5000); return; } return; } if (Mode == EControlMode.Preview) { if (_mouseButton == MouseButtons.Middle) { TransformationMatrix *= Matrix.CreateTranslation(e.X - _lastMousePosition.X, e.Y - _lastMousePosition.Y, 0); } } _mouseShift = new Vector2(e.X, e.Y) - _lastMousePosition; if (_mouseButton == MouseButtons.Left&& _selectedNode != null) { var inverted = Matrix.Invert(TransformationMatrix); var transformed = Vector2.Transform(new Vector2(e.X, e.Y), inverted); _selectedNode.SetPosition(transformed); } _lastMousePosition = new Vector2(e.X, e.Y); Invalidate(); } private Graphics _currentGraphics; private Workflow.Datatypes.HawkeyeImage? _image; private Vector2 _mouseShift; private INode _selectedNode; protected override void OnPaint(PaintEventArgs pe) { if (Image == null) { pe.Graphics.Clear(Color.Black); return; } try { using (Graphics g = Graphics.FromImage(_backBuffer)) { _currentGraphics = g; g.Clear(Color.Black); var img = Image.ImageData.ToBitmap(); var pos = Vector2.Transform(new Vector2(0, 0), TransformationMatrix); var size = Vector2.Transform(new Vector2(img.Width, img.Height), TransformationMatrix); g.DrawImage(img, pos.X, pos.Y, size.X - pos.X, size.Y - pos.Y); foreach (IGraphicsElement element in GraphicsElements) { element.Draw(this); } } pe.Graphics.DrawImage(_backBuffer, 0, 0); } catch { } } protected override void OnPaintBackground(PaintEventArgs pevent) { //base.OnPaintBackground(pevent); } protected override void OnResize(EventArgs e) { if (Width == 0) return; _backBuffer = new Bitmap(Width, Height); } public void DrawNode(Vector2 location) { var imageCoordSpace = Vector2.Transform(location, TransformationMatrix); var start = imageCoordSpace - new Vector2(NODE_SIZE/2, NODE_SIZE / 2); var end = (imageCoordSpace + new Vector2(NODE_SIZE / 2, NODE_SIZE / 2))-start; _currentGraphics.FillRectangle(Brushes.White,start.X,start.Y,end.X,end.Y); _currentGraphics.DrawRectangle(Pens.Black,start.X,start.Y,end.X,end.Y); } public void DrawArrow(Vector2 location, Vector2 direction) { var imageCoordSpaceLocation = Vector2.Transform(location, TransformationMatrix); var imageCoordSpaceSize = Vector2.Transform(location + direction, TransformationMatrix); var arrowPen = new Pen(Color.Aqua, 2); arrowPen.EndCap = LineCap.Custom; arrowPen.CustomEndCap = new AdjustableArrowCap(5,5,true); _currentGraphics.DrawLine(arrowPen, imageCoordSpaceLocation.X, imageCoordSpaceLocation.Y, imageCoordSpaceSize.X, imageCoordSpaceSize.Y); } public void DrawRectangle(Vector2 start, Vector2 size, bool isGood) { var imageCoordSpaceLocation = Vector2.Transform(start, TransformationMatrix); var imageCoordSpaceSize = Vector2.Transform(start+size, TransformationMatrix)-imageCoordSpaceLocation; var pen = new Pen(isGood ? Color.Green : Color.Red, 2); _currentGraphics.DrawRectangle(pen, imageCoordSpaceLocation.X, imageCoordSpaceLocation.Y, imageCoordSpaceSize.X, imageCoordSpaceSize.Y); } public void DrawLine(Vector2 start, Vector2 end,LineType type) { var coordStart = Vector2.Transform(start, TransformationMatrix); var coordEnd = Vector2.Transform(end, TransformationMatrix); var pen = new Pen(type ==LineType.Good? Color.Green:(type==LineType.Bad? Color.Red:Color.Aqua),2); _currentGraphics.DrawLine(pen,coordStart.X,coordStart.Y,coordEnd.X,coordEnd.Y); } public void DrawPoly(Vector2[] points, bool isGood) { var pen = new Pen(isGood ? Color.Green : Color.Red, 3); var coords = points.Select(x=> Vector2.Transform(x, TransformationMatrix)).Select(x => new PointF(x.X, x.Y)).ToArray(); _currentGraphics.DrawPolygon(pen, coords); } public void DrawCross(Vector2 location) { var startH=Vector2.Transform(location-new Vector2(10,0),TransformationMatrix); var endH = Vector2.Transform(location+new Vector2(10,0), TransformationMatrix); var startV =Vector2.Transform(location-new Vector2(0,20), TransformationMatrix); var endV =Vector2.Transform(location+new Vector2(0,20), TransformationMatrix); var pen = new Pen(Color.Aqua, 1); _currentGraphics.DrawLine(pen, startH.X, startH.Y, endH.X, endH.Y); _currentGraphics.DrawLine(pen, startV.X, startV.Y, endV.X, endV.Y); } } }