在Preview窗口中实现以下功能:

- 当鼠标指向热力图上的任意点时
- 将该点映射到原始温度数据矩阵中的对应数据点
- 计算该数据点在热力图上覆盖的2×2像素区域
- 在该区域上绘制半透明红色覆盖层,直观显示数据点的覆盖范围
This commit is contained in:
zqm
2026-01-19 10:06:42 +08:00
parent b5a28bb420
commit a1978f183d
2 changed files with 145 additions and 1 deletions

View File

@@ -33,6 +33,6 @@ using System.Runtime.InteropServices;
//通过使用 "*",如下所示: //通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.2.5")] [assembly: AssemblyVersion("1.0.2.5")]
[assembly: AssemblyFileVersion("1.0.2.7")] [assembly: AssemblyFileVersion("1.0.2.8")]
// NuGet包相关信息已在项目文件中配置 // NuGet包相关信息已在项目文件中配置

View File

@@ -14,12 +14,27 @@ namespace JoyD.Windows.CS
/// </summary> /// </summary>
public partial class preview : Form 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>
/// 初始化预览窗口的构造函数 /// 初始化预览窗口的构造函数
/// </summary> /// </summary>
public preview() public preview()
{ {
InitializeComponent(); InitializeComponent();
previewImageBox.MouseMove += PreviewImageBox_MouseMove;
previewImageBox.MouseLeave += PreviewImageBox_MouseLeave;
previewImageBox.Paint += PreviewImageBox_Paint;
} }
/// <summary> /// <summary>
@@ -48,6 +63,9 @@ namespace JoyD.Windows.CS
{ {
previewImageBox.Image = (Image)image.Clone(); previewImageBox.Image = (Image)image.Clone();
} }
// 图像更新后,确保重绘以保持覆盖层正确
previewImageBox.Invalidate();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -62,6 +80,11 @@ namespace JoyD.Windows.CS
{ {
base.OnFormClosed(e); base.OnFormClosed(e);
// 移除事件绑定
previewImageBox.MouseMove -= PreviewImageBox_MouseMove;
previewImageBox.MouseLeave -= PreviewImageBox_MouseLeave;
previewImageBox.Paint -= PreviewImageBox_Paint;
// 释放图像资源 // 释放图像资源
if (previewImageBox.Image != null) if (previewImageBox.Image != null)
{ {
@@ -69,5 +92,126 @@ namespace JoyD.Windows.CS
previewImageBox.Image = null; 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);
}
}
}
} }
} }