需求:
wpf中,使用canvas和Image实现
- 鼠标放在图片上鼠标滚轮缩放图片
- 鼠标拖动图片
- 鼠标右键点击图片,在图片上画点,所画的点跟随图片缩放和移动
- 鼠标悬浮在图片上,实时获取该点上的hsv值(opencv的hsv值)
- 能够获取原图比例对应点的坐标值
实现效果如下图所示
实现
我看到大部分的实现都是在Canvas
上进行缩放移动,但这个对我的需求来说可能有点麻烦(或许可能是我能力还不够。。)
后来我尝试了在外层套一个Grid
,把缩放移动的事件都放在这个Grid
上,Image
不放在Canvas
里,然后使Canvas
和Image
的大小相同,那么进行缩放和移动的时候,Canvas
和Image
就会同时的缩放和移动
代码
直接放代码,注解看代码里的
!!!注意,以下代码基于源码做了删减,主要展示核心部分,可能有些地方的异常处理也删掉了,不要直接复制整个代码去运行,部分核心代码可以直接复制。以下也没有给出viewmodel的实现,相信读者有能力实现的
.xaml的代码
<Grid Background="Black">
<Grid.RowDefinitions>
<RowDefinition Height="20*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" ClipToBounds="True">
<Grid MouseWheel="Grid_MouseWheel"
RenderTransformOrigin="0.5, 0.5"
MouseLeftButtonDown="Grid_MouseLeftButtonDown"
MouseMove="Grid_MouseMove"
>
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scaleTransform" ScaleX="1" ScaleY="1" />
<TranslateTransform x:Name="translateTransform" />
</TransformGroup>
</Grid.RenderTransform>
<!-- Image的Source值,这里我是使用mvvm的模式,绑定了Image值,你也可以把Source直接替换成你的图片路径 -->
<Image x:Name="image" Source="{Binding Image}"
MouseMove="Image_MouseMove"
MouseRightButtonDown="Image_MouseRightButtonDown"/>
<!-- 这里Canvas的宽高一定要和Image的一样大小 -->
<Canvas x:Name="canvas"
Width="{Binding ActualWidth,ElementName=image}"
Height="{Binding ActualHeight,ElementName=image}"
>
<!-- 这里使用mvvm模式绑定所画的点,只要往DrawPoints里添加点,Canvas就会把点渲染出来 -->
<ItemsControl ItemsSource="{Binding DrawPoints}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="2" Height="2" Fill="Orange"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
</Grid>
</Grid>
<!-- 实时显示opencv中的hsv值 -->
<TextBlock Grid.Row="1" x:Name="txtHSV" Foreground="#fff" FontSize="18" VerticalAlignment="Bottom" Margin="5 0 0 0"/>
</Grid>
.cs代码
using CommunityToolkit.Mvvm.DependencyInjection;
using Console.ViewModels.Screw;
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
namespace Console.Views
{
public partial class ScrewPreProcessGlue2d : Page
{
private ViewModel ViewModel { get; set; }
private Mat imgMat;
private System.Windows.Point origin;
private System.Windows.Point start;
public ScrewPreProcessGlue2d()
{
InitializeComponent();
}
private void Grid_MouseWheel(object sender, MouseWheelEventArgs e)
{
double zoom = e.Delta > 0 ? .2 : -.2;
// 限制缩小,最小只能缩到1
if (scaleTransform.ScaleX + zoom <= 1)
{
scaleTransform.ScaleX = 1;
scaleTransform.ScaleY = 1;
translateTransform.X = 0;
translateTransform.Y = 0;
return;
}
scaleTransform.ScaleX += zoom;
scaleTransform.ScaleY += zoom;
}
private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
start = e.GetPosition(canvas);
origin = new System.Windows.Point(translateTransform.X, translateTransform.Y);
}
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (scaleTransform.ScaleX == 1 && scaleTransform.ScaleY == 1) return;
if (e.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyUp(Key.LeftCtrl) && Keyboard.IsKeyUp(Key.RightCtrl)))
{
Vector v = start - e.GetPosition(canvas);
translateTransform.X = origin.X - v.X;
translateTransform.Y = origin.Y - v.Y;
}
}
// 右键点击画点
private void Image_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var position = e.GetPosition(image); // 这里获取到的坐标值是图片在界面缩放比为1时的坐标值
ViewModel.DrawPoints.Add(position); // 把点添加到viewmodel的DrawPoints中,界面会自动把点画出来
// 如果想要获取原图比例下的坐标值,可以执行这个方法
var scaled_point = GetScaledPosition(position,image);
}
private void Image_MouseMove(object sender, MouseEventArgs e)
{
imgMat = ViewModel.Image.ToMat();
System.Windows.Point p = e.GetPosition(originImgViewer);
if (p.X >= 0 && p.X < image.ActualWidth &&
p.Y >= 0 && p.Y < image.ActualHeight)
{
// Convert mouse position to image pixel coordinates
int x = (int)(p.X * (imgMat.Width / image.ActualWidth));
int y = (int)(p.Y * (imgMat.Height / image.ActualHeight));
// Get pixel color at mouse position
Vec3b pixel = imgMat.At<Vec3b>(y, x);
// Convert pixel color to HSV
Mat bgr = new Mat(1, 1, MatType.CV_8UC3, new Scalar(pixel.Item0, pixel.Item1, pixel.Item2));
Mat hsv = new Mat();
Cv2.CvtColor(bgr, hsv, ColorConversionCodes.BGR2HSV);
// Extract HSV values
var hsvValues = hsv.Get<Vec3b>(0, 0);
// Update UI with HSV values
txtHSV.Text = $"H: {hsvValues.Item0}, S: {hsvValues.Item1}, V: {hsvValues.Item2}";
}
}
// position是界面图片比例下的坐标值,element是控件名
private System.Windows.Point GetScaledPosition(System.Windows.Point position, FrameworkElement element)
{
var actualWidth = element.ActualWidth; // 图片实际渲染的宽高
var actualHeight = element.ActualHeight;
var imageWidth = ViewModel.Image.PixelWidth; // 原图的宽高
var imageHeight = ViewModel.Image.PixelHeight;
var xRatio = imageWidth / actualWidth;
var yRatio = imageHeight / actualHeight;
var scaledX = position.X * xRatio;
var scaledY = position.Y * yRatio;
return new System.Windows.Point(scaledX, scaledY);
}
}
}