WPF编写类似ImagJ看图软件
整机程序结构

<Window x:Class="WpfApp3.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp3" mc:Ignorable="d" Title="MainWindow" Height="60" Width="300" Closing="Window_Closing"> <Grid> <Border> <Grid Drop="ImagePanel_Drop" AllowDrop="true" > <TextBlock x:Name="TbGray" Margin="10,0,0,0" /> <TextBlock x:Name="TbTiShi" Text="请把图像拖到此处" Foreground="#92A3BB" VerticalAlignment="Center" HorizontalAlignment="Center"/> </Grid> </Border> </Grid> </Window>

using OpenCvSharp; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Window = System.Windows.Window; namespace WpfApp3 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { List<ImageShow> imageShows = new List<ImageShow>(); ImageShow ActiveImageShow; bool IsClosing = false; public MainWindow() { InitializeComponent(); Task.Run(() => { while (IsClosing == false) { Thread.Sleep(1); this.Dispatcher.Invoke(new Action(() => { foreach (ImageShow imageShow in imageShows) { if (imageShow.IsActive && imageShow != ActiveImageShow) { ActiveImageShow = imageShow; } } if(ActiveImageShow != null) TbGray.Text = ActiveImageShow.XYGray; })); } }); } private BitmapSource MatToBitmapSource(Mat mat,ref byte[] gray) { PixelFormat pf = PixelFormats.Gray8; List<System.Windows.Media.Color> colors = new List<System.Windows.Media.Color>(); colors.Add(System.Windows.Media.Colors.Red); colors.Add(System.Windows.Media.Colors.Blue); colors.Add(System.Windows.Media.Colors.Green); BitmapPalette myPalette = new BitmapPalette(colors); int rawStride = (mat.Width * pf.BitsPerPixel + 7) / 8; mat.GetArray(out gray); //force garbage collection System.GC.Collect(); System.GC.WaitForPendingFinalizers(); return BitmapSource.Create(mat.Width, mat.Height, 96, 96, pf, myPalette, gray, rawStride); } public void HandleImageBuff(Mat mat, string name) { //在画布中添加图像 var imageUser = new ImageUser(); imageUser.Img = new Image(); imageUser.Img.Source = MatToBitmapSource(mat, ref imageUser.ImgGray); imageUser.Img.Stretch = Stretch.Uniform; imageUser.Img.Width = mat.Width; imageUser.Img.Height = mat.Height; imageUser.Img.SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.NearestNeighbor);//取消抗锯齿 imageUser.ImgName = name; var imageShow = new ImageShow(imageUser); imageShow.Show(); imageShows.Add(imageShow); } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { IsClosing = true; foreach (ImageShow imageShow in imageShows) imageShow.Close(); } private void ImagePanel_Drop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { TbTiShi.Text = ""; // Note that you can have more than one file. string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); var OriginalMat = Cv2.ImRead(files[0], ImreadModes.Grayscale); //显示图片 HandleImageBuff(OriginalMat, "原图"); Mat imageSobel = new Mat(); Cv2.Sobel(OriginalMat, imageSobel, MatType.CV_8U, 1, 0);//Sobel算子计算梯度值 HandleImageBuff(imageSobel, "SobelX"); Cv2.Sobel(OriginalMat, imageSobel, MatType.CV_8U, 0, 1); HandleImageBuff(imageSobel, "SobelY"); Cv2.Sobel(OriginalMat, imageSobel, MatType.CV_8U, 1, 1); HandleImageBuff(imageSobel, "SobelXY"); } } } public class ImageUser { public Image Img; public string ImgName; public byte[] ImgGray; } }

<Window x:Class="WpfApp3.ImageShow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp3" mc:Ignorable="d" Title="ImageShow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Border x:Name="outside" Background="Gray" PreviewMouseDown="outsidewrapper_PreviewMouseDown" PreviewMouseMove="outsidewrapper_PreviewMouseMove" PreviewMouseUp="outside_PreviewMouseUp" PreviewMouseWheel="outside_PreviewMouseWheel" ClipToBounds="True" ScrollViewer.HorizontalScrollBarVisibility="Visible"> <Canvas x:Name="inside" Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type Border}}}" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type Border}}}" ScrollViewer.HorizontalScrollBarVisibility="Visible"> <Canvas.RenderTransform> <TransformGroup/> </Canvas.RenderTransform> </Canvas> </Border> </Grid> </Window>

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace WpfApp3 { /// <summary> /// ImageShow.xaml 的交互逻辑 /// </summary> public partial class ImageShow : Window { public string XYGray; System.Windows.Point previousPoint; bool isTranslateStart = false; ImageUser ImageUser = new ImageUser(); public ImageShow(ImageUser imageUser) { InitializeComponent(); ImageUser = imageUser; this.Title = ImageUser.ImgName; this.Height = 500; this.Width = this.Height * ImageUser.Img.Width / ImageUser.Img.Height; inside.Children.Add(ImageUser.Img); Canvas.SetTop(ImageUser.Img, 0); Canvas.SetLeft(ImageUser.Img, 0); Task.Run(() => { this.Dispatcher.Invoke(new Action(() => { FullScreen(); })); }); } private void FullScreen() { Double IamgeWidth = ImageUser.Img.Width; Double IamgeHeight = ImageUser.Img.Height; TransformGroup tg = inside.RenderTransform as TransformGroup; System.Windows.Media.Matrix value = new System.Windows.Media.Matrix(); //全屏 value.M11 = outside.ActualWidth / IamgeWidth / tg.Value.M11;//缩放倍数为Border的尺寸 value.M22 = outside.ActualHeight / IamgeHeight / tg.Value.M22; ////图像比例不变全屏 固定宽度 //value.M11 = outside.ActualWidth / IamgeWidth / tg.Value.M11;//缩放倍数为Border的尺寸 //value.M22 = outside.ActualWidth / IamgeWidth * IamgeHeight / IamgeHeight / tg.Value.M22; ////图像比例不变全屏 固定高度 //value.M11 = outside.ActualHeight / IamgeHeight * IamgeWidth / IamgeWidth / tg.Value.M11;//缩放倍数为Border的尺寸 //value.M22 = outside.ActualHeight / IamgeHeight / tg.Value.M22; value.OffsetX = (0 - tg.Value.OffsetX) * value.M11;//调整偏移XY到(0,0) value.OffsetY = (0 - tg.Value.OffsetY) * value.M22; MatrixTransform matrixTransform = new MatrixTransform(value);//添加矩阵变换 tg.Children.Add(matrixTransform); } private void outsidewrapper_PreviewMouseDown(object sender, MouseButtonEventArgs e) { if (e.MiddleButton == MouseButtonState.Pressed || e.LeftButton == MouseButtonState.Pressed || e.RightButton == MouseButtonState.Pressed) { previousPoint = e.GetPosition(outside); isTranslateStart = true; } e.Handled = true; } private void outsidewrapper_PreviewMouseMove(object sender, MouseEventArgs e) { if (e.MiddleButton == MouseButtonState.Pressed || e.LeftButton == MouseButtonState.Pressed || e.RightButton == MouseButtonState.Pressed) { if (isTranslateStart) { System.Windows.Point currentPoint = e.GetPosition(outside); //不能用 inside,必须用outside System.Windows.Vector v = currentPoint - previousPoint; TransformGroup tg = inside.RenderTransform as TransformGroup; tg.Children.Add(new TranslateTransform(v.X, v.Y)); //centerX和centerY用外部包装元素的坐标,不能用内部被变换的Canvas元素的坐标 previousPoint = currentPoint; } } else { //适用于静止图像 System.Windows.Point point2 = Mouse.GetPosition(inside);//获取Canvas坐标 UInt32 x, y; x = (UInt32)point2.X; y = (UInt32)point2.Y; XYGray = $"x:{x} y:{y} gray:- ";//显示坐标值 if (x >= 0 && x < ImageUser.Img.Width && y >= 0 && y < ImageUser.Img.Height) XYGray = $"x:{x} y:{y} gray:{ImageUser.ImgGray[y * (int)ImageUser.Img.Width + x]}"; } e.Handled = true; } private void outside_PreviewMouseUp(object sender, MouseButtonEventArgs e) { if (e.MiddleButton == MouseButtonState.Pressed && e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released) { if (isTranslateStart) { isTranslateStart = false; } } e.Handled = true; } private void outside_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { System.Windows.Point currentPoint = e.GetPosition(outside); //不能用 inside,必须用outside TransformGroup tg = inside.RenderTransform as TransformGroup; double s = ((double)e.Delta) / 1000.0 + 1.0; if (ImageUser.Img.Height * tg.Value.M22 < 200 || ImageUser.Img.Width * tg.Value.M11 < 200) { if (s > 1.0)//当图像像素宽度和高度小于200的时候只能放大,不再缩小 tg.Children.Add(new ScaleTransform(s, s, currentPoint.X, currentPoint.Y)); } else tg.Children.Add(new ScaleTransform(s, s, currentPoint.X, currentPoint.Y)); e.Handled = true; } } }
此程序打开图像用的库是OpenCvSharp4与OpenCvSharp4.runtime.win
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现