在Preview窗口中实现以下功能:
- 当鼠标指向热力图上的任意点时 - 将该点映射到原始温度数据矩阵中的对应数据点 - 计算该数据点在热力图上覆盖的2×2像素区域 - 在该区域上绘制半透明红色覆盖层,直观显示数据点的覆盖范围
This commit is contained in:
@@ -33,6 +33,6 @@ using System.Runtime.InteropServices;
|
||||
//通过使用 "*",如下所示:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.2.5")]
|
||||
[assembly: AssemblyFileVersion("1.0.2.7")]
|
||||
[assembly: AssemblyFileVersion("1.0.2.8")]
|
||||
|
||||
// NuGet包相关信息已在项目文件中配置
|
||||
|
||||
@@ -14,12 +14,27 @@ namespace JoyD.Windows.CS
|
||||
/// </summary>
|
||||
public partial class preview : Form
|
||||
{
|
||||
// 原始温度数据矩阵尺寸
|
||||
private const int DATA_WIDTH = 256;
|
||||
private const int DATA_HEIGHT = 192;
|
||||
// 热力图显示尺寸
|
||||
private const int DISPLAY_WIDTH = 512;
|
||||
private const int DISPLAY_HEIGHT = 384;
|
||||
|
||||
// 鼠标当前指向的数据点坐标
|
||||
private Point _currentDataPoint = new Point(-1, -1);
|
||||
// 用于保护_currentDataPoint访问的锁对象
|
||||
private readonly object _dataPointLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化预览窗口的构造函数
|
||||
/// </summary>
|
||||
public preview()
|
||||
{
|
||||
InitializeComponent();
|
||||
previewImageBox.MouseMove += PreviewImageBox_MouseMove;
|
||||
previewImageBox.MouseLeave += PreviewImageBox_MouseLeave;
|
||||
previewImageBox.Paint += PreviewImageBox_Paint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,6 +63,9 @@ namespace JoyD.Windows.CS
|
||||
{
|
||||
previewImageBox.Image = (Image)image.Clone();
|
||||
}
|
||||
|
||||
// 图像更新后,确保重绘以保持覆盖层正确
|
||||
previewImageBox.Invalidate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -62,6 +80,11 @@ namespace JoyD.Windows.CS
|
||||
{
|
||||
base.OnFormClosed(e);
|
||||
|
||||
// 移除事件绑定
|
||||
previewImageBox.MouseMove -= PreviewImageBox_MouseMove;
|
||||
previewImageBox.MouseLeave -= PreviewImageBox_MouseLeave;
|
||||
previewImageBox.Paint -= PreviewImageBox_Paint;
|
||||
|
||||
// 释放图像资源
|
||||
if (previewImageBox.Image != null)
|
||||
{
|
||||
@@ -69,5 +92,126 @@ namespace JoyD.Windows.CS
|
||||
previewImageBox.Image = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标移动事件处理
|
||||
private void PreviewImageBox_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (previewImageBox.Image == null) return;
|
||||
|
||||
// 计算鼠标在图像上的坐标(考虑图像缩放)
|
||||
Point imagePoint = GetImagePoint(e.Location);
|
||||
|
||||
// 映射到原始数据点坐标
|
||||
Point dataPoint = MapToDataPoint(imagePoint);
|
||||
|
||||
// 使用锁保护_currentDataPoint的访问
|
||||
lock (_dataPointLock)
|
||||
{
|
||||
// 如果数据点发生变化,重绘控件
|
||||
if (_currentDataPoint != dataPoint)
|
||||
{
|
||||
_currentDataPoint = dataPoint;
|
||||
previewImageBox.Invalidate(); // 触发Paint事件重绘
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标离开事件处理
|
||||
private void PreviewImageBox_MouseLeave(object sender, EventArgs e)
|
||||
{
|
||||
// 使用锁保护_currentDataPoint的访问
|
||||
lock (_dataPointLock)
|
||||
{
|
||||
_currentDataPoint = new Point(-1, -1);
|
||||
previewImageBox.Invalidate(); // 触发Paint事件重绘
|
||||
}
|
||||
}
|
||||
|
||||
// 将控件坐标转换为图像坐标
|
||||
private Point GetImagePoint(Point controlPoint)
|
||||
{
|
||||
if (previewImageBox.Image == null)
|
||||
return controlPoint;
|
||||
|
||||
// 计算图像在控件中的缩放比例
|
||||
// SizeMode = StretchImage 将图像拉伸填满整个PictureBox
|
||||
// 控件左上角对应图像左上角,控件右下角对应图像右下角
|
||||
float scaleX = (float)previewImageBox.Image.Width / previewImageBox.ClientSize.Width;
|
||||
float scaleY = (float)previewImageBox.Image.Height / previewImageBox.ClientSize.Height;
|
||||
|
||||
// 计算图像坐标
|
||||
return new Point(
|
||||
(int)(controlPoint.X * scaleX),
|
||||
(int)(controlPoint.Y * scaleY)
|
||||
);
|
||||
}
|
||||
|
||||
// 将图像坐标映射到原始数据点坐标
|
||||
private Point MapToDataPoint(Point imagePoint)
|
||||
{
|
||||
// 确保坐标在有效范围内
|
||||
int x = Math.Max(0, Math.Min(imagePoint.X, DISPLAY_WIDTH - 1));
|
||||
int y = Math.Max(0, Math.Min(imagePoint.Y, DISPLAY_HEIGHT - 1));
|
||||
|
||||
// 计算原始数据点坐标(整数除法,向下取整)
|
||||
return new Point(x / 2, y / 2);
|
||||
}
|
||||
|
||||
// 将原始数据点坐标映射到热力图覆盖区域
|
||||
private Rectangle GetDataPointCoverageArea(Point dataPoint)
|
||||
{
|
||||
// 确保数据点在有效范围内
|
||||
int x = Math.Max(0, Math.Min(dataPoint.X, DATA_WIDTH - 1));
|
||||
int y = Math.Max(0, Math.Min(dataPoint.Y, DATA_HEIGHT - 1));
|
||||
|
||||
// 计算覆盖区域(2×2像素)
|
||||
return new Rectangle(x * 2, y * 2, 2, 2);
|
||||
}
|
||||
|
||||
// Paint事件处理函数
|
||||
private void PreviewImageBox_Paint(object sender, PaintEventArgs e)
|
||||
{
|
||||
// 边界检查:如果没有图像,不需要绘制覆盖层
|
||||
if (previewImageBox.Image == null)
|
||||
return;
|
||||
|
||||
// 使用锁保护_currentDataPoint的访问并进行有效性检查
|
||||
Point currentDataPoint;
|
||||
lock (_dataPointLock)
|
||||
{
|
||||
currentDataPoint = _currentDataPoint;
|
||||
// 如果没有有效的数据点,不需要绘制覆盖层
|
||||
if (currentDataPoint.X < 0 || currentDataPoint.Y < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算数据点在图像坐标系中的覆盖区域
|
||||
Rectangle imageArea = GetDataPointCoverageArea(currentDataPoint);
|
||||
|
||||
// 转换为控件坐标系
|
||||
float scaleX = (float)previewImageBox.ClientSize.Width / previewImageBox.Image.Width;
|
||||
float scaleY = (float)previewImageBox.ClientSize.Height / previewImageBox.Image.Height;
|
||||
|
||||
Rectangle controlArea = new Rectangle(
|
||||
(int)(imageArea.X * scaleX),
|
||||
(int)(imageArea.Y * scaleY),
|
||||
(int)(imageArea.Width * scaleX),
|
||||
(int)(imageArea.Height * scaleY)
|
||||
);
|
||||
|
||||
// 使用using语句确保GDI+资源正确释放
|
||||
using (Brush semiTransparentRed = new SolidBrush(Color.FromArgb(128, Color.Red)))
|
||||
{
|
||||
// 绘制覆盖层
|
||||
e.Graphics.FillRectangle(semiTransparentRed, controlArea);
|
||||
|
||||
// 使用using语句确保Pen资源正确释放
|
||||
using (Pen borderPen = new Pen(Color.Red, 1))
|
||||
{
|
||||
// 可选:绘制矩形边框以增强视觉效果
|
||||
e.Graphics.DrawRectangle(borderPen, controlArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user