我编写这个组件来渲染相机的流。在流之上,用户可以绘制一个或多个形状(通过界面管理)。可用的形状有:
除了绘制形状外,用户还可以选择一个形状并更改大小和位置。用户还可以看到图像的最热点,然后该点将在图像内连续移动。 图片不断更新,由事件处理:
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;
}
}
}
该代码可以工作,但是当来自相机的流是高清的或绘制了许多形状时,整个应用程序会变慢,给用户带来不便。您有什么意见和建议可以给我最好地优化这个组件吗?
优化的第一条规则是测量。我们可以猜测为什么代码整天都很慢,但是运行分析器实际上会告诉您问题出在哪里。
我看不到将 16 位值转换为 8 位值的代码在哪里。但这是一个值得关注的领域,您可能希望缓存此转换的结果以加快重绘速度。
for (int i = 0; i < ThermalImage.GetLength(0); ++i)
.GetLength
非常慢,不应该为每个像素调用。我建议根本不要对图像数据使用多维数组,因为它们以列主格式存储像素,这与几乎所有其他图像格式使用的行主格式相反。我的建议是使用一维数组,并自己进行索引,即[y * width + x]
。