这是一个之前遗留的问题。wpf里面有很多很多的东西,我以前用的真的只是其中很小的一个角落都不到。
需求背景:图片来源于相机拍摄,由于对像素要求,拍出来的图像素比较高,原图尺寸为30722048,以目前的电脑屏幕,很多都是显示不了这么大的,比如1440 900啊,1280 * 1024啊这种的,
在全屏情况下图像的显示都是很小的,图片细节看不清。
为了满足看清图片细节的需求,我们想要做一个放大镜。windows系统下有自带的放大镜功能,在一定程度上可以满足需求,但是实际情况是,将宽度为三千多的图放到一千多的屏幕上,图像被压缩了,
损失了很多点,这种情况下,即使使用了放大镜,也只能实现将1个像素放大到2个像素那么大,而那些损失的点找不回来了。
当时做的时候,受放大镜这个名词的束缚,在网络上搜索wpf放大镜的实现,这种情况下,找到了一个类似于实现windows放大镜功能的方法,勉(ying)为(fu)其(jiao)难(chai),就拿来用了。
有一定的效果,但是我心里知道,这个是不行的。
以后说不定还会有用,所以代码还是贴在这里,代码来源已经不记得了(以后找到补上地址),反正是别人那边搬运来的,我记得当时是直接下载了一份代码,那份代码里面太多修饰性的东西了,我不需要就全都去掉了。
主要是使用VisualBrush来实现的。
这是我调整过之后的源码,这份代码勉为其难也算是我部分原创吧。
页面部分:
<Canvas Grid.Row="1" Grid.Column="0" VerticalAlignment="Top" HorizontalAlignment="Left"> <Canvas Name="magnifierCanvas" IsHitTestVisible="False" Visibility="{Binding ElementName=checkEnableMagnifier,Path=IsChecked,Converter={StaticResource BoolToVis}}"> <Rectangle Width="149" Height="149" Name="magnifierEllipse" StrokeThickness="1"> <Rectangle.Fill> <VisualBrush ViewboxUnits="Absolute" Viewbox="0,0,149,149" ViewportUnits="RelativeToBoundingBox" Viewport="0,0,1,1"/> </Rectangle.Fill> <Rectangle.Stroke> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Offset="0" Color="#AAA" /> <GradientStop Offset="1" Color="#111" /> </LinearGradientBrush> </Rectangle.Stroke> </Rectangle> <Line X1="73" X2="77" Y1="75" Y2="75" StrokeThickness="1" Stroke="Cyan" Visibility="Visible"/> <Line X1="75" X2="75" Y1="73" Y2="77" StrokeThickness="1" Stroke="Cyan" Visibility="Visible"/> </Canvas> </Canvas>
后台代码:
private double _delta = 0; private void Image_MouseWheel(object sender, MouseWheelEventArgs e) { if (_delta <= 0 && e.Delta < 0) return; if (_delta >= 80 && e.Delta > 0) return; _delta += (double)e.Delta / 10; if (_delta > 130) _delta = 130; Point pos = e.MouseDevice.GetPosition(image); if (magnifierEllipse != null) { Rect viewBox = vb.Viewbox; double val = (double)magnifierEllipse.Width - _delta; viewBox.Width = val; viewBox.Height = val; double xoffset = viewBox.Width / 2.0; double yoffset = viewBox.Height / 2.0; viewBox.X = pos.X - xoffset; viewBox.Y = pos.Y - yoffset; vb.Viewbox = viewBox; Canvas.SetLeft(magnifierCanvas, pos.X - magnifierEllipse.Width / 2); Canvas.SetTop(magnifierCanvas, pos.Y - magnifierEllipse.Height / 2); } }
当时遇到了一个小小的坑,Image控件必须放在一个和这个Canvas一样大的容器里面,不然显示的时候总是会错位。
完整代码就不贴了,这是一个大程序的一小部分。
昨天在看一份图像处理的资料的时候,一开始就提到了压缩图片尺寸导致细节丢失的问题,我突然意识到我之前的那种做法特别的坑,今天重新想了一下,我为什么要抓住放大镜这一点不放呢。
也不是,其实我之前的问题在于我要怎么在Image控件上显示某张图片上的指定偏移量指定宽高的图片区域,一直没有找到这个方法。
今天突然醒悟了,我傻了吧唧的,其实可以使用图片裁切的方法,把原图在指定位置指定大小的内容裁切下来显示不就好了么。。。。
代码大部分也是来源网络,这个方法感觉还可以,我之前用了另一种方法,内存没管理好,竟然爆表了。。。。。
源代码来源:https://blog.csdn.net/qq_18995513/article/details/67637521
下面贴的代码是我根据实际情况微调之后的,更符合我现阶段的情况。
找找和源代码哪里不一样?
// 图像工具类 public static class SystemUtils { /// <summary> /// 切图 /// </summary> /// <param name="bitmapSource">图源</param> /// <param name="cut">切割区域</param> /// <returns></returns> public static BitmapSource CutImage(BitmapSource bitmapSource, Int32Rect cut) { //计算Stride int max = cut.Width > cut.Height ? cut.Width : cut.Height; //var stride = bitmapSource.Format.BitsPerPixel * cut.Width / 8; var stride = bitmapSource.Format.BitsPerPixel * max / 8; //声明字节数组 byte[] data = new byte[stride * max]; //调用CopyPixels bitmapSource.CopyPixels(cut, data, stride, 0); return BitmapSource.Create(cut.Width, cut.Height, 0, 0, PixelFormats.Bgr32, null, data, stride); } // ImageSource --> Bitmap public static System.Drawing.Bitmap ImageSourceToBitmap(ImageSource imageSource) { BitmapSource m = (BitmapSource)imageSource; System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(m.PixelWidth, m.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); System.Drawing.Imaging.BitmapData data = bmp.LockBits( new System.Drawing.Rectangle(System.Drawing.Point.Empty, bmp.Size), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); m.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride); bmp.UnlockBits(data); return bmp; } // Bitmap --> BitmapImage public static BitmapImage BitmapToBitmapImage(Bitmap bitmap) { using (MemoryStream stream = new MemoryStream()) { bitmap.Save(stream, ImageFormat.Bmp); stream.Position = 0; BitmapImage result = new BitmapImage(); result.BeginInit(); // According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed." // Force the bitmap to load right now so we can dispose the stream. result.CacheOption = BitmapCacheOption.OnLoad; result.StreamSource = stream; result.EndInit(); result.Freeze(); return result; } } }
不一样的地方就是我遇到的坑,真的好坑啊,没有人和我遇到一样的问题吗?我就看到我有这个问题。。。。。
在切割宽度小于高度的时候,就是会有一个异常,改掉之后就好啦。
其他关于放大缩小后坐标的计算的代码就不贴了。其实也算了好久,终于算对了。为了省事,我把鼠标所在的位置作为放大后的局部图片的左上角坐标了,比中心坐标稍微少算点东西。
本来呢,事情到这边就应该告一段落了,放大功能已经实现了,但是问题是,其实我的需求不仅仅是把图片放大,图片上可能会有一些新画上去的东西,这个东西有可能会需要擦掉,
上面这种操作只将图片显示在了放大框内,而我后期添加的那些线条啊文字啊,全都没有显示,怎么办呢?第一种方法的好处就是它直接把这个控件里面的所有东西都放大了,而第二种放大仅仅把控件里的图片放大了。唉,还是没有解决问题呀。