具有形状绘制和编辑功能的自定义图片框

问题描述 投票:0回答:1

我编写这个组件来渲染相机的流。在流之上,用户可以绘制一个或多个形状(通过界面管理)。可用的形状有:

  1. 矩形
  2. 椭圆
  3. 折线

除了绘制形状外,用户还可以选择一个形状并更改大小和位置。用户还可以看到图像的最热点,然后该点将在图像内连续移动。 图片不断更新,由事件处理:

UpdateRadiometricImage

这是我的组件的完整代码:

using Imager.IRBinding;
using Imager.Services;
using Imager.Shapes;
using Imager.Types;
using Imager.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Web.UI.WebControls.Expressions;
using System.Windows.Forms;

namespace Imager.Components
{
    public class RadiometricImage : PictureBox, IDisposable
    {
        // -------- PER IL DISEGNO
        internal string Serial { get; set; }
        internal List<IShape> Shapes = new List<IShape>(); // Forme permanenti
        
        internal List<IShape> SelectedShapes { get; set; } = new List<IShape>(); // Forme selezionate

        public ShapeType ShapeToDraw { get; set; } = ShapeType.None;
        public ModeType CurrentMode { get; set; } = ModeType.None;
        public PolylineShape CurrentPolyline { get; set; } = null; // Polilinea temporanea
        public bool IsDrawingPolyline { get; set; } = false; // Flag per il disegno polilinea
        
        private Point currentCursorPoint; // Posizione corrente del cursore
        private Point? startPoint = null; // Punto iniziale del disegno
        private IShape tempShape = null; // Forma temporanea
        private Point? selectedHandler = null; // Handler selezionato per modifica
        private bool isDragging = false; // Flag per indicare il trascinamento
        private Point dragStartPoint; // Punto iniziale del trascinamento
        private Point? activeResizeHandler = null; // Handler attivo per il resize
        private int selectedResizeHandler; // Handler selezionato per il resize
        // --------

        // ------------ AREA DI MISURA PANNELLO
        public event EventHandler<ShapeChangedEventArgs> ShapeChanged;
        public event EventHandler ThermalImageUpdated;
        // -----------

        // --------- MOSTRA PUNTO CALDO GLOBALE
        public bool ShowHotSpot { get; set; } = false;
        private readonly PointShape _hotSpot = new PointShape();
        // ---------

        private bool _disposed = false;
        private Rectangle _imageBounds;
        private readonly ToolTip _toolTip = new ToolTip();

        private Bitmap _paletteImage = null;
        public Bitmap PaletteImage
        {
            get => _paletteImage;
            set
            {
                Image = _paletteImage = value;
                CalculateImageBounds();
            }
        }

        public ushort[,] ThermalImage { get; set; } = null;
        public EvoIRFrameMetadata Metadata { get; set; }

        public RadiometricImage()
        {
            Console.WriteLine("RadiometricImage created.");

            BackColor = Color.Black;
            Dock = DockStyle.Fill;
            DoubleBuffered = true;
            SizeMode = PictureBoxSizeMode.Zoom;

            Resize += (s, e) =>
            {
                Console.WriteLine("Resize event triggered.");
                CalculateImageBounds();
            };

            MouseMove += RadiometricImage_MouseMove;
            MouseLeave += RadiometricImage_MouseLeave;

            MouseDown += RadiometricImage_MouseDown;
            MouseUp += RadiometricImage_MouseUp;
            
            
            Paint += RadiometricImage_Paint;
        }

        internal void UpdateRadiometricImage(ThermalPaletteImage img)
        {
            PaletteImage = img.PaletteImage;
            Metadata = img.IRFrameMetadata;
            ThermalImage = img.ThermalImage;
            
            ThermalImageUpdated?.Invoke(this, EventArgs.Empty); // Notifica il cambiamento
        }

        private void RadiometricImage_MouseLeave(object sender, EventArgs e)
        {
            Console.WriteLine("Mouse fouri dall'immagine, nascondo tooltip");
            _toolTip.Hide(this);
        }

        private void RadiometricImage_Paint(object sender, PaintEventArgs e)
        {
            if (ShowHotSpot)
            {
                int maxI = 0, maxJ = 0;
                ushort max = ThermalImage[0, 0];

                for (int i = 0; i < ThermalImage.GetLength(0); ++i)
                {
                    for (int j = 0; j < ThermalImage.GetLength(1); ++j)
                    {
                        if (ThermalImage[i, j] > max)
                        {
                            max = ThermalImage[i, j];
                            maxI = i;
                            maxJ = j;
                        }
                    }
                }

                _hotSpot.Position = new Point(maxJ, maxI);
                _hotSpot.Name = $"{Conversion.RealTemperatureValue(max)} °C";
                _hotSpot.Draw(e.Graphics, FromImageToContainer, Color.Black);
                DrawShapeName(e.Graphics, _hotSpot, Color.Black);
            }


            foreach (var shape in Shapes)
            {
                Console.WriteLine("shape");

                shape.Draw(e.Graphics, FromImageToContainer);
                DrawShapeName(e.Graphics, shape);
                if (SelectedShapes.Contains(shape))
                {
                    DrawShapeHandlers(e.Graphics, shape);
                }
            }

            if (ShapeToDraw == ShapeType.Polyline && CurrentPolyline != null)
            {
                Console.WriteLine("polyline shape");
                CurrentPolyline.Draw(e.Graphics, FromImageToContainer);
                DrawPolylinePreview(e.Graphics);
                DrawShapeHandlers(e.Graphics, CurrentPolyline);
            }

            tempShape?.Draw(e.Graphics, FromImageToContainer);
        }

        internal void RadiometricImage_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                if (SelectedShapes.Any())
                {
                    foreach (var shape in SelectedShapes.ToList())
                    {
                        RemoveShape(shape);
                    }

                    SelectedShapes.Clear();
                    Invalidate();
                }
            }
            else if (e.KeyCode == Keys.Escape)
            {
                ResetDrawingState();
                Invalidate();
            }

            if (SelectedShapes.Any())
            {
                const int moveStep = 1;
                switch (e.KeyCode)
                {
                    case Keys.Left:
                        MoveSelectedShapes(new Point(-moveStep, 0), true);
                        break;
                    case Keys.Right:
                        MoveSelectedShapes(new Point(moveStep, 0), true);
                        break;
                    case Keys.Up:
                        MoveSelectedShapes(new Point(0, -moveStep), true);
                        break;
                    case Keys.Down:
                        MoveSelectedShapes(new Point(0, moveStep), true);
                        break;
                }
            }
        }

        private void RadiometricImage_MouseUp(object sender, MouseEventArgs e)
        {
            Console.WriteLine($"MouseUp at {e.Location}");

            if (isDragging)
            {
                isDragging = false;
                Cursor = Cursors.Default;
            }
            else if (activeResizeHandler.HasValue)
            {
                activeResizeHandler = null;
                Cursor = Cursors.Default;
            }
            else if ((ShapeToDraw == ShapeType.Rectangle || ShapeToDraw == ShapeType.Ellipse) && tempShape != null)
            {
                tempShape.Name = $"Area {Shapes.Count + 1}";
                AddShape(tempShape);
                tempShape = null;
                startPoint = null;
            }
            selectedHandler = null;
            Cursor = Cursors.Default;
            
            
            // Invalidate();
        }

        private void RadiometricImage_MouseMove(object sender, MouseEventArgs e)
        {
            Console.WriteLine($"MouseMove at {e.Location}");

            if (CurrentMode == ModeType.None && _imageBounds.Contains(e.Location))
            {
                var position = FromContainerToImage(e.Location);
                double temperature = CameraService.GetPixelValue(ThermalImage, position);
                _toolTip.Show($"{temperature} °C", this, e.X + 10, e.Y + 10);
            }
            else
            {
                _toolTip.Hide(this);
            }

            currentCursorPoint = e.Location;
            
            if (activeResizeHandler.HasValue && SelectedShapes.Count == 1 && activeResizeHandler.Value != e.Location)
            {
                Console.WriteLine($"Active Handler: {activeResizeHandler.Value}");
                var shape = SelectedShapes.First();
                if (_imageBounds.Contains(e.Location))
                {

                    shape.Resize(FromContainerToImage(activeResizeHandler.Value), FromContainerToImage(e.Location)); // Ridimensiona la forma
                    var aux = shape.GetHandlers();
                    activeResizeHandler = FromImageToContainer(aux.ElementAt(selectedResizeHandler)); // Aggiorna la posizione dell'handler
                    Invalidate();
                }

            }
            else if (isDragging && SelectedShapes.Any())
            {
                MoveSelectedShapes(e.Location);
                dragStartPoint = e.Location;
            }
            else
            {
                foreach (var shape in SelectedShapes)
                {
                    var pos = FromContainerToImage(e.Location);
                    bool onHandler = false;
                    if (shape is PolylineShape polyline)
                    {
                        foreach (var handler in polyline.GetHandlers())
                        {
                            if (DrawHelper.IsCloseToPoint(e.Location, FromImageToContainer(handler)))
                            {
                                Cursor = Cursors.Hand;
                                onHandler = true;
                                break;
                            }
                        }
                    }
                    else if (!(shape is PointShape point))
                    {
                        foreach (var handler in shape.GetHandlers())
                        {
                            if (DrawHelper.IsCloseToPoint(e.Location, FromImageToContainer(handler)))
                            {
                                Cursor = DrawHelper.GetResizeCursor(handler, shape); // Ottieni il cursore corretto
                                onHandler = true;
                                break;
                            }
                        }
                    }
                    if (!onHandler)
                    {
                        Cursor = Cursors.Default;
                    }
                }
            }
            if (CurrentMode == ModeType.Draw)
            {
                if (ShapeToDraw == ShapeType.Polyline && selectedHandler.HasValue) MovePolylineHandler(FromContainerToImage(e.Location));
                if ((ShapeToDraw == ShapeType.Rectangle || ShapeToDraw == ShapeType.Ellipse) && startPoint.HasValue) UpdateTemporaryShape(e.Location);

                Invalidate();
            }
        } 


        private void RadiometricImage_MouseDown(object sender, MouseEventArgs e)
        {
            Console.WriteLine($"MouseDown at {e.Location}");

            if (CurrentMode == ModeType.Select)
            {
                HandleSelectionMouseDown(e.Location);
                if (SelectedShapes.Any() && isDragging)
                {
                    Cursor = Cursors.SizeAll;
                }
            }
            
            if (CurrentMode == ModeType.Draw)
            {
                if (_imageBounds.Contains(e.Location))
                {
                    switch (ShapeToDraw)
                    {
                        case ShapeType.Point:
                            AddPointShape(e.Location);
                            break;
                        case ShapeType.Rectangle:
                        case ShapeType.Ellipse:
                            startPoint = FromContainerToImage(e.Location);
                            break;
                        case ShapeType.Polyline:
                            HandlePolylineMouseDown(e);
                            break;
                        default:
                            break;
                    }
                }
            }
        }


        public void AddShape(IShape shape)
        {
            Shapes.Add(shape);
            OnShapeAdded(shape);
            Invalidate(); // Per ridisegnare shape.GetRegion(FromImageToContainer)
        }

        public void RemoveShape(IShape shape)
        {
            if (Shapes.Remove(shape))
            {
                OnShapeRemoved(shape);
                Invalidate(); // Per ridisegnare shape.GetRegion(FromImageToContainer)
            }
        }

        #region HELPERS

        private Point FromContainerToImage(Point src)
        {
            if (Image == null) return Point.Empty;

            float xRatio = (float)(src.X - _imageBounds.Left) / _imageBounds.Width;
            float yRatio = (float)(src.Y - _imageBounds.Top) / _imageBounds.Height;

            return new Point((int)(xRatio * Image.Width), (int)(yRatio * Image.Height));
        }

        private Point FromImageToContainer(Point src)
        {
            if (Image == null) return Point.Empty;

            float xRatio = (float)src.X / Image.Width;
            float yRatio = (float)src.Y / Image.Height;

            return new Point((int)(xRatio * _imageBounds.Width + _imageBounds.Left), (int)(yRatio * _imageBounds.Height + _imageBounds.Top));
        }

        private void CalculateImageBounds()
        {
            if (Image == null)
            {
                _imageBounds = new Rectangle(0, 0, 0, 0);
                return;
            }

            float xRatio = (float)Width / Image.Width;
            float yRatio = (float)Height / Image.Height;

            float ratio = Math.Min(xRatio, yRatio);

            int width = (int)(Image.Width * ratio);
            int height = (int)(Image.Height * ratio);

            _imageBounds = new Rectangle((Width - width) / 2, (Height - height) / 2, width, height);
        }

        private void DrawHandler(Graphics g, Point point)
        {
            const int size = 10;
            var rect = new Rectangle(point.X - size / 2, point.Y - size / 2, size, size);
            using (var brush = new SolidBrush(SystemColors.Highlight))
            {
                g.FillRectangle(brush, rect);
            }
        }

        private void DrawShapeHandlers(Graphics g, IShape shape)
        {
            foreach (var handler in shape.GetHandlers().Select(x => FromImageToContainer(x)))
            {
                DrawHandler(g, handler);
            }
        }

        private void ResetDrawingState()
        {
            if (ShapeToDraw == ShapeType.Polyline && IsDrawingPolyline)
            {
                CurrentPolyline = null;
                IsDrawingPolyline = false;
            }
            else if ((ShapeToDraw == ShapeType.Rectangle || ShapeToDraw == ShapeType.Ellipse) && tempShape != null)
            {
                tempShape = null;
                startPoint = null;
            }
            else if (SelectedShapes.Any())
            {
                SelectedShapes.Clear();
            }

            // CurrentMode = ModeType.None;
        }

        private void HandlePolylineMouseDown(MouseEventArgs e)
        {
            if (CurrentPolyline != null)
            {
                var point = FromContainerToImage(e.Location);

                if (CurrentPolyline.Points.Count > 0 && DrawHelper.IsCloseToPoint(point, CurrentPolyline.Points[0]))
                {
                    CurrentPolyline.ClosePolyline();
                    CurrentPolyline.IsClosed = true;
                    CurrentPolyline.Name = $"Area {Shapes.Count + 1}";
                    AddShape(CurrentPolyline);
                    CurrentPolyline = null;
                    IsDrawingPolyline = true;
                }
                else
                {
                    CurrentPolyline.Points.Add(point);
                }
            }
        }

        private void AddPointShape(Point location)
        {
            var pointShape = new PointShape() { Name = $"Area {Shapes.Count + 1}", Position = FromContainerToImage(location) };
            AddShape(pointShape);
        }

        private void UpdateTemporaryShape(Point location)
        {
            if (startPoint.HasValue && _imageBounds.Contains(location))
            {
                var rect = DrawHelper.GetRectangleFromPoints(startPoint.Value, FromContainerToImage(location));
                switch (ShapeToDraw)
                {
                    case ShapeType.Rectangle:
                        tempShape = new RectangleShape { Rect = rect };
                        break;
                    case ShapeType.Ellipse:
                        tempShape = new EllipseShape { Bounds = rect };
                        break;
                }
            }
        }

        private void MoveSelectedShapes(Point location, bool keyboard = false)
        {
            var bounds = new Rectangle(0, 0, Image.Width, Image.Height);
            if (keyboard)
            {
                foreach (var shape in SelectedShapes)
                {
                    shape.Move(bounds, location);
                }
            }
            else
            {
                var convertedLocation = FromContainerToImage(location);
                var convartedStartPoint = FromContainerToImage(dragStartPoint);

                var deltaX = convertedLocation.X - convartedStartPoint.X;
                var deltaY = convertedLocation.Y - convartedStartPoint.Y;

                foreach (var shape in SelectedShapes)
                {
                    shape.Move(bounds, new Point(deltaX, deltaY));
                }
                dragStartPoint = location;
            }
        }

        private void MovePolylineHandler(Point location)
        {
            if (CurrentPolyline != null && selectedHandler.HasValue)
            {
                var index = CurrentPolyline.Points.IndexOf(selectedHandler.Value);
                if (index >= 0)
                {
                    CurrentPolyline.Points[index] = location;
                }
            }
        }

        private void DrawShapeName(Graphics g, IShape shape, Color color = default)
        {
            var center = shape.NamePosition(FromImageToContainer, Image.Height);

            if (color == default) color = Color.White;

            using (var font = new Font("Arial", 14))
            {
                using (var brush = new SolidBrush(color))
                {
                    g.DrawString(shape.Name, font, brush, center);
                }
            }
        }

        private void HandleSelectionMouseDown(Point location)
        {
            SelectedShapes.Clear();
            activeResizeHandler = null; // Resetta l'handler attivo
            
            foreach (var shape in Shapes)
            {
                if (!(shape is PointShape point))
                {
                    int i = 0;
                    foreach (var handler in shape.GetHandlers())
                    {
                        var h = FromImageToContainer(handler);
                        if (DrawHelper.IsCloseToPoint(location, h))
                        {
                            SelectedShapes.Add(shape);
                            activeResizeHandler = h; // Memorizza l'handler attivo per il resize
                            selectedResizeHandler = i;
                            Console.WriteLine($"Handler: {h}");
                            return;
                        }
                        i++;
                    }
                }
                if (shape.PointOnBound(FromContainerToImage(location)))
                {
                    SelectedShapes.Add(shape);
                    dragStartPoint = location;
                    isDragging = true;
                    Invalidate();
                    return;
                }
            }
        }

        private void DrawPolylinePreview(Graphics g)
        {
            if (CurrentPolyline != null && CurrentPolyline.Points.Count > 0)
            {
                var lastPoint = CurrentPolyline.Points.Last();
                if (DrawHelper.IsCloseToPoint(FromContainerToImage(currentCursorPoint), CurrentPolyline.Points[0]))
                {
                    g.DrawLine(Pens.White, FromImageToContainer(lastPoint), FromImageToContainer(CurrentPolyline.Points[0]));
                }
                else
                {
                    g.DrawLine(Pens.Gray, FromImageToContainer(lastPoint), currentCursorPoint);
                }
            }
        }

        #endregion

        #region AREA_DI_MISURA

        private void OnShapeAdded(IShape shape)
        {
            ShapeChanged?.Invoke(this, new ShapeChangedEventArgs(shape, AreaOperation.Added));
        }

        private void OnShapeRemoved(IShape shape)
        {
            ShapeChanged?.Invoke(this, new ShapeChangedEventArgs(shape, AreaOperation.Removed));
        }

        #endregion

        protected override void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (PaletteImage != null)
                    {
                        PaletteImage.Dispose();
                        PaletteImage = null;
                    }

                    if (Shapes != null)
                    {
                        Shapes.Clear();
                        Shapes = null;
                    }

                    if (SelectedShapes != null)
                    {
                        SelectedShapes.Clear();
                        SelectedShapes = null;
                    }

                    if (ThermalImage != null) ThermalImage = null;
                    if (CurrentPolyline != null) CurrentPolyline = null;

                    Resize -= (s, e) => CalculateImageBounds();
                    MouseMove -= RadiometricImage_MouseMove;
                    MouseDown -= RadiometricImage_MouseDown;
                    MouseUp -= RadiometricImage_MouseUp;
                    Paint -= RadiometricImage_Paint;
                }
            }

            base.Dispose(disposing);

            _disposed = true;
        }
    }
}

该代码可以工作,但是当来自相机的流是高清的或绘制了许多形状时,整个应用程序会变慢,给用户带来不便。您有什么意见和建议可以给我最好地优化这个组件吗?

c# .net winforms picturebox
1个回答
0
投票

优化的第一条规则是测量。我们可以猜测为什么代码整天都很慢,但是运行分析器实际上会告诉您问题出在哪里。

我看不到将 16 位值转换为 8 位值的代码在哪里。但这是一个值得关注的领域,您可能希望缓存此转换的结果以加快重绘速度。

for (int i = 0; i < ThermalImage.GetLength(0); ++i)

.GetLength
非常慢,不应该为每个像素调用。我建议根本不要对图像数据使用多维数组,因为它们以列主格式存储像素,这与几乎所有其他图像格式使用的行主格式相反。我的建议是使用一维数组,并自己进行索引,即
[y * width + x]

© www.soinside.com 2019 - 2024. All rights reserved.