Files
2025-07-14 12:03:59 +02:00

330 lines
13 KiB
C#

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<IGraphicsElement> GraphicsElements { get; set; }=new List<IGraphicsElement>();
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<Vec3b>((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<byte>((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);
}
}
}