基于WPF实现裁剪图像功能

Leona ·
更新时间:2024-09-20
· 1415 次阅读

WPF 实现裁剪图像

框架使用.NET4 至 .NET6

Visual Studio 2022

使用 Canvas 展示选择的裁剪图片

使用 4 个 Rectangle 设置未选中区域分别是左上右下

中间展示当前的裁剪区域使用了 Border 移动

左右移动使用 Canvas.SetLeft

上下移动使用 Canvas.SetTop

Border 获取裁剪区域获取GetLeftGetTopBorderWidthHeight

拉伸 Border 使用了之前截图控件使用的装饰器 ScreenCutAdorner

新增装饰器不允许拉伸超出 Canvas 画布

实现代码

1)新建 CropImage.cs 控件代码如下:

using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using WPFDevelopers.Helpers; namespace WPFDevelopers.Controls {     [TemplatePart(Name = CanvasTemplateName, Type = typeof(Canvas))]     [TemplatePart(Name = RectangleLeftTemplateName, Type = typeof(Rectangle))]     [TemplatePart(Name = RectangleTopTemplateName, Type = typeof(Rectangle))]     [TemplatePart(Name = RectangleRightTemplateName, Type = typeof(Rectangle))]     [TemplatePart(Name = RectangleBottomTemplateName, Type = typeof(Rectangle))]     [TemplatePart(Name = BorderTemplateName, Type = typeof(Border))]     public class CropImage : Control     {         private const string CanvasTemplateName = "PART_Canvas";         private const string RectangleLeftTemplateName = "PART_RectangleLeft";         private const string RectangleTopTemplateName = "PART_RectangleTop";         private const string RectangleRightTemplateName = "PART_RectangleRight";         private const string RectangleBottomTemplateName = "PART_RectangleBottom";         private const string BorderTemplateName = "PART_Border";         private BitmapFrame bitmapFrame;         private Rectangle _rectangleLeft, _rectangleTop, _rectangleRight, _rectangleBottom;         private Border _border;         private Canvas _canvas;         public ImageSource Source         {             get { return (ImageSource)GetValue(SourceProperty); }             set { SetValue(SourceProperty, value); }         }         public static readonly DependencyProperty SourceProperty =             DependencyProperty.Register("Source", typeof(ImageSource), typeof(CropImage), new PropertyMetadata(null, OnSourceChanged));         public Rect CurrentRect         {             get { return (Rect)GetValue(CurrentRectProperty); }             private set { SetValue(CurrentRectProperty, value); }         }         public static readonly DependencyProperty CurrentRectProperty =             DependencyProperty.Register("CurrentRect", typeof(Rect), typeof(CropImage), new PropertyMetadata(null));         public ImageSource CurrentAreaBitmap         {             get { return (ImageSource)GetValue(CurrentAreaBitmapProperty); }             private set { SetValue(CurrentAreaBitmapProperty, value); }         }         public static readonly DependencyProperty CurrentAreaBitmapProperty =             DependencyProperty.Register("CurrentAreaBitmap", typeof(ImageSource), typeof(CropImage), new PropertyMetadata(null));         private AdornerLayer adornerLayer;         private ScreenCutAdorner screenCutAdorner;         private bool isDragging;         private double offsetX, offsetY;         private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)         {             var crop = (CropImage)d;             if (crop != null)                 crop.DrawImage();         }         static CropImage()         {             DefaultStyleKeyProperty.OverrideMetadata(typeof(CropImage),                 new FrameworkPropertyMetadata(typeof(CropImage)));         }         public override void OnApplyTemplate()         {             base.OnApplyTemplate();             _canvas = GetTemplateChild(CanvasTemplateName) as Canvas;             _rectangleLeft = GetTemplateChild(RectangleLeftTemplateName) as Rectangle;             _rectangleTop = GetTemplateChild(RectangleTopTemplateName) as Rectangle;             _rectangleRight = GetTemplateChild(RectangleRightTemplateName) as Rectangle;             _rectangleBottom = GetTemplateChild(RectangleBottomTemplateName) as Rectangle;             _border = GetTemplateChild(BorderTemplateName) as Border;             DrawImage();         }         void DrawImage()         {             if (Source == null)             {                 _border.Visibility = Visibility.Collapsed;                 if (adornerLayer == null) return;                 adornerLayer.Remove(screenCutAdorner);                 screenCutAdorner = null;                 adornerLayer = null;                 return;             }             _border.Visibility = Visibility.Visible;             var bitmap = (BitmapImage)Source;             bitmapFrame = ControlsHelper.CreateResizedImage(bitmap, (int)bitmap.Width, (int)bitmap.Height, 0);             _canvas.Width = bitmap.Width;             _canvas.Height = bitmap.Height;             _canvas.Background = new ImageBrush(bitmap);             _border.Width = bitmap.Width * 0.2;             _border.Height = bitmap.Height * 0.2;             var cx = _canvas.Width / 2 - _border.Width / 2;             var cy = _canvas.Height / 2 - _border.Height / 2;             Canvas.SetLeft(_border, cx);             Canvas.SetTop(_border, cy);             if (adornerLayer != null) return;             adornerLayer = AdornerLayer.GetAdornerLayer(_border);             screenCutAdorner = new ScreenCutAdorner(_border);             adornerLayer.Add(screenCutAdorner);             _border.SizeChanged -= Border_SizeChanged;             _border.SizeChanged += Border_SizeChanged;             _border.MouseDown -= Border_MouseDown;             _border.MouseDown += Border_MouseDown;             _border.MouseMove -= Border_MouseMove;             _border.MouseMove += Border_MouseMove;             _border.MouseUp -= Border_MouseUp;             _border.MouseUp += Border_MouseUp;         }         private void Border_MouseUp(object sender, MouseButtonEventArgs e)         {             isDragging = false;             var draggableControl = sender as UIElement;             draggableControl.ReleaseMouseCapture();         }         private void Border_MouseDown(object sender, MouseButtonEventArgs e)         {             if (!isDragging)             {                 isDragging = true;                 var draggableControl = sender as UIElement;                 var position = e.GetPosition(this);                 offsetX = position.X - Canvas.GetLeft(draggableControl);                 offsetY = position.Y - Canvas.GetTop(draggableControl);                 draggableControl.CaptureMouse();             }         }         private void Border_MouseMove(object sender, MouseEventArgs e)         {             if (isDragging && e.LeftButton == MouseButtonState.Pressed)             {                 var draggableControl = sender as UIElement;                 var position = e.GetPosition(this);                 var x = position.X - offsetX;                 x = x < 0 ? 0 : x;                 x = x + _border.Width > _canvas.Width ? _canvas.Width - _border.Width : x;                 var y = position.Y - offsetY;                 y = y < 0 ? 0 : y;                 y = y + _border.Height > _canvas.Height ? _canvas.Height - _border.Height : y;                 Canvas.SetLeft(draggableControl, x);                 Canvas.SetTop(draggableControl, y);                 Render();             }         }         void Render()         {             var cy = Canvas.GetTop(_border);             cy = cy < 0 ? 0 : cy;             var borderLeft = Canvas.GetLeft(_border);             borderLeft = borderLeft < 0 ? 0 : borderLeft;             _rectangleLeft.Width = borderLeft;             _rectangleLeft.Height = _border.ActualHeight;             Canvas.SetTop(_rectangleLeft, cy);             _rectangleTop.Width = _canvas.Width;             _rectangleTop.Height = cy;             var rx = borderLeft + _border.ActualWidth;             rx = rx > _canvas.Width ? _canvas.Width : rx;             _rectangleRight.Width = _canvas.Width - rx;             _rectangleRight.Height = _border.ActualHeight;             Canvas.SetLeft(_rectangleRight, rx);             Canvas.SetTop(_rectangleRight, cy);             var by = cy + _border.ActualHeight;             by = by < 0 ? 0 : by;             _rectangleBottom.Width = _canvas.Width;             var rby = _canvas.Height - by;             _rectangleBottom.Height = rby < 0 ? 0 : rby;             Canvas.SetTop(_rectangleBottom, by);             var bitmap = CutBitmap();             if (bitmap == null) return;             var frame = BitmapFrame.Create(bitmap);             CurrentAreaBitmap = frame;         }         private void Border_SizeChanged(object sender, SizeChangedEventArgs e)         {             Render();         }         private CroppedBitmap CutBitmap()         {             var width = _border.Width;             var height = _border.Height;             if (double.IsNaN(width) || double.IsNaN(height))                 return null;             var left = Canvas.GetLeft(_border);             var top = Canvas.GetTop(_border);             CurrentRect = new Rect(left, top, width, height);             return new CroppedBitmap(bitmapFrame,                new Int32Rect((int)CurrentRect.X, (int)CurrentRect.Y, (int)CurrentRect.Width, (int)CurrentRect.Height));         }     } }

2)修复 ScreenCutAdorner.cs 装饰器不允许超过 Canvas 代码如下:

using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; namespace WPFDevelopers.Controls {     public class ScreenCutAdorner : Adorner     {         private const double THUMB_SIZE = 15;         private const double MINIMAL_SIZE = 20;         private readonly Thumb lc;         private readonly Thumb tl;         private readonly Thumb tc;         private readonly Thumb tr;         private readonly Thumb rc;         private readonly Thumb br;         private readonly Thumb bc;         private readonly Thumb bl;         private readonly VisualCollection visCollec;         private readonly Canvas canvas;         public ScreenCutAdorner(UIElement adorned) : base(adorned)         {             canvas = FindParent(adorned) as Canvas;             visCollec = new VisualCollection(this);             visCollec.Add(lc = GetResizeThumb(Cursors.SizeWE, HorizontalAlignment.Left, VerticalAlignment.Center));             visCollec.Add(tl = GetResizeThumb(Cursors.SizeNWSE, HorizontalAlignment.Left, VerticalAlignment.Top));             visCollec.Add(tc = GetResizeThumb(Cursors.SizeNS, HorizontalAlignment.Center, VerticalAlignment.Top));             visCollec.Add(tr = GetResizeThumb(Cursors.SizeNESW, HorizontalAlignment.Right, VerticalAlignment.Top));             visCollec.Add(rc = GetResizeThumb(Cursors.SizeWE, HorizontalAlignment.Right, VerticalAlignment.Center));             visCollec.Add(br = GetResizeThumb(Cursors.SizeNWSE, HorizontalAlignment.Right, VerticalAlignment.Bottom));             visCollec.Add(bc = GetResizeThumb(Cursors.SizeNS, HorizontalAlignment.Center, VerticalAlignment.Bottom));             visCollec.Add(bl = GetResizeThumb(Cursors.SizeNESW, HorizontalAlignment.Left, VerticalAlignment.Bottom));         }         private static UIElement FindParent(UIElement element)         {             DependencyObject obj = element;             obj = VisualTreeHelper.GetParent(obj);             return obj as UIElement;         }         protected override int VisualChildrenCount => visCollec.Count;         protected override Size ArrangeOverride(Size finalSize)         {             var offset = THUMB_SIZE / 2;             var sz = new Size(THUMB_SIZE, THUMB_SIZE);             lc.Arrange(new Rect(new Point(-offset, AdornedElement.RenderSize.Height / 2 - offset), sz));             tl.Arrange(new Rect(new Point(-offset, -offset), sz));             tc.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width / 2 - offset, -offset), sz));             tr.Arrange(new Rect(new Point(AdornedElement.RenderSize.Width - offset, -offset), sz));             rc.Arrange(new Rect(                 new Point(AdornedElement.RenderSize.Width - offset, AdornedElement.RenderSize.Height / 2 - offset),                 sz));             br.Arrange(new Rect(                 new Point(AdornedElement.RenderSize.Width - offset, AdornedElement.RenderSize.Height - offset), sz));             bc.Arrange(new Rect(                 new Point(AdornedElement.RenderSize.Width / 2 - offset, AdornedElement.RenderSize.Height - offset),                 sz));             bl.Arrange(new Rect(new Point(-offset, AdornedElement.RenderSize.Height - offset), sz));             return finalSize;         }         private void Resize(FrameworkElement frameworkElement)         {             if (double.IsNaN(frameworkElement.Width))                 frameworkElement.Width = frameworkElement.RenderSize.Width;             if (double.IsNaN(frameworkElement.Height))                 frameworkElement.Height = frameworkElement.RenderSize.Height;         }         private Thumb GetResizeThumb(Cursor cur, HorizontalAlignment hor, VerticalAlignment ver)         {             var thumb = new Thumb             {                 Width = THUMB_SIZE,                 Height = THUMB_SIZE,                 HorizontalAlignment = hor,                 VerticalAlignment = ver,                 Cursor = cur,                 Template = new ControlTemplate(typeof(Thumb))                 {                     VisualTree = GetFactory(new SolidColorBrush(Colors.White))                 }             };             var maxWidth = double.IsNaN(canvas.Width) ? canvas.ActualWidth : canvas.Width;             var maxHeight = double.IsNaN(canvas.Height) ? canvas.ActualHeight : canvas.Height;             thumb.DragDelta += (s, e) =>             {                 var element = AdornedElement as FrameworkElement;                 if (element == null)                     return;                 Resize(element);                 switch (thumb.VerticalAlignment)                 {                     case VerticalAlignment.Bottom:                         if (element.Height + e.VerticalChange > MINIMAL_SIZE)                         {                             var newHeight = element.Height + e.VerticalChange;                             var top = Canvas.GetTop(element) + newHeight;                             if (newHeight > 0 && top <= canvas.ActualHeight)                                 element.Height = newHeight;                         }                         break;                     case VerticalAlignment.Top:                         if (element.Height - e.VerticalChange > MINIMAL_SIZE)                         {                             var newHeight = element.Height - e.VerticalChange;                             var top = Canvas.GetTop(element);                             if (newHeight > 0 && top + e.VerticalChange >= 0)                             {                                 element.Height = newHeight;                                 Canvas.SetTop(element, top + e.VerticalChange);                             }                         }                         break;                 }                 switch (thumb.HorizontalAlignment)                 {                     case HorizontalAlignment.Left:                         if (element.Width - e.HorizontalChange > MINIMAL_SIZE)                         {                             var newWidth = element.Width - e.HorizontalChange;                             var left = Canvas.GetLeft(element);                             if (newWidth > 0 && left + e.HorizontalChange >= 0)                             {                                 element.Width = newWidth;                                 Canvas.SetLeft(element, left + e.HorizontalChange);                             }                         }                         break;                     case HorizontalAlignment.Right:                         if (element.Width + e.HorizontalChange > MINIMAL_SIZE)                         {                             var newWidth = element.Width + e.HorizontalChange;                             var left = Canvas.GetLeft(element) + newWidth;                             if (newWidth > 0 && left <= canvas.ActualWidth)                                 element.Width = newWidth;                         }                         break;                 }                 e.Handled = true;             };             return thumb;         }         private FrameworkElementFactory GetFactory(Brush back)         {             var fef = new FrameworkElementFactory(typeof(Ellipse));             fef.SetValue(Shape.FillProperty, back);             fef.SetValue(Shape.StrokeProperty, DrawingContextHelper.Brush);             fef.SetValue(Shape.StrokeThicknessProperty, (double)2);             return fef;         }         protected override Visual GetVisualChild(int index)         {             return visCollec[index];         }     } }

3)新建 CropImage.xaml 代码如下:

<ResourceDictionary     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:controls="clr-namespace:WPFDevelopers.Controls">     <ResourceDictionary.MergedDictionaries>         <ResourceDictionary Source="Basic/ControlBasic.xaml" />     </ResourceDictionary.MergedDictionaries>     <Style         x:Key="WD.CropImage"         BasedOn="{StaticResource WD.ControlBasicStyle}"         TargetType="{x:Type controls:CropImage}">         <Setter Property="Template">             <Setter.Value>                 <ControlTemplate TargetType="{x:Type controls:CropImage}">                     <Canvas x:Name="PART_Canvas">                         <Rectangle x:Name="PART_RectangleLeft" Style="{DynamicResource WD.ScreenCutRectangleStyle}" />                         <Rectangle x:Name="PART_RectangleTop" Style="{DynamicResource WD.ScreenCutRectangleStyle}" />                         <Rectangle x:Name="PART_RectangleRight" Style="{DynamicResource WD.ScreenCutRectangleStyle}" />                         <Rectangle x:Name="PART_RectangleBottom" Style="{DynamicResource WD.ScreenCutRectangleStyle}" />                         <Border                             x:Name="PART_Border"                             Background="Transparent"                             BorderBrush="{DynamicResource WD.PrimaryNormalSolidColorBrush}"                             BorderThickness="2"                             Cursor="SizeAll" />                     </Canvas>                 </ControlTemplate>             </Setter.Value>         </Setter>     </Style>     <Style BasedOn="{StaticResource WD.CropImage}" TargetType="{x:Type controls:CropImage}" /> </ResourceDictionary>

4)新建 CropImageExample.xaml 代码如下:

<UserControl     x:Class="WPFDevelopers.Samples.ExampleViews.CropImageExample"     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:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"     d:DesignHeight="450"     d:DesignWidth="800"     mc:Ignorable="d">         <Grid>             <Grid.RowDefinitions>                 <RowDefinition Height="*" />                 <RowDefinition Height="Auto" />             </Grid.RowDefinitions>             <Grid.ColumnDefinitions>                 <ColumnDefinition />                 <ColumnDefinition />             </Grid.ColumnDefinitions>             <wd:CropImage                 Name="MyCropImage"                 Grid.Row="0"                 Grid.Column="0" />             <Image                 Grid.Column="1"                 Width="{Binding CurrentRect.Width, ElementName=MyCropImage}"                 Height="{Binding CurrentRect.Height, ElementName=MyCropImage}"                 VerticalAlignment="Center"                 Source="{Binding CurrentAreaBitmap, ElementName=MyCropImage}"                 Stretch="Uniform" />             <StackPanel                 Grid.Row="1"                 Grid.ColumnSpan="2"                 HorizontalAlignment="Center"                 Orientation="Horizontal">                 <Button                     Margin="0,20,10,20"                     Click="OnImportClickHandler"                     Content="选择图片"                     Style="{StaticResource WD.PrimaryButton}" />                 <Button                     Margin="0,20,10,20"                     Click="BtnSave_Click"                     Content="保存图片"                     Style="{StaticResource WD.SuccessPrimaryButton}" />             </StackPanel>         </Grid> </UserControl>

5)新建 CropImageExample.xaml.cs 代码如下:

选择图片不允许大于 1M

如果选择的图片尺寸宽或高大于 500 ,则会修改图片尺寸一半宽高

using Microsoft.Win32; using System; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Media.Imaging; namespace WPFDevelopers.Samples.ExampleViews {     public partial class CropImageExample : UserControl     {         public CropImageExample()         {             InitializeComponent();         }         double ConvertBytesToMB(long bytes)         {             return (double)bytes / (1024 * 1024);         }         private void OnImportClickHandler(object sender, RoutedEventArgs e)         {             var openFileDialog = new OpenFileDialog();             openFileDialog.Filter = "图像文件(*.jpg;*.jpeg;*.png;)|*.jpg;*.jpeg;*.png;";             if (openFileDialog.ShowDialog() == true)             {                 var fileInfo = new FileInfo(openFileDialog.FileName);                 var fileSize = fileInfo.Length;                 var mb = ConvertBytesToMB(fileSize);                 if (mb > 1)                 {                     WPFDevelopers.Controls.MessageBox.Show("图片不能大于 1M ", "提示", MessageBoxButton.OK, MessageBoxImage.Error);                     return;                 }                 var bitmap = new BitmapImage();                 bitmap.BeginInit();                 bitmap.CacheOption = BitmapCacheOption.OnLoad;                 bitmap.UriSource = new Uri(openFileDialog.FileName, UriKind.Absolute);                 bitmap.EndInit();                 if (bitmap.PixelWidth > 500 || bitmap.PixelHeight > 500)                 {                     var width = (int)(bitmap.PixelWidth * 0.5);                     var height = (int)(bitmap.PixelHeight * 0.5);                     var croppedBitmap = new CroppedBitmap(bitmap, new Int32Rect(0, 0, width, height));                     var bitmapNew = new BitmapImage();                     bitmapNew.BeginInit();                     bitmapNew.DecodePixelWidth = width;                     bitmapNew.DecodePixelHeight = height;                     var memoryStream = new MemoryStream();                     var encoder = new JpegBitmapEncoder();                     encoder.Frames.Add(BitmapFrame.Create(croppedBitmap.Source));                     encoder.Save(memoryStream);                     memoryStream.Seek(0, SeekOrigin.Begin);                     bitmapNew.StreamSource = memoryStream;                     bitmapNew.EndInit();                     MyCropImage.Source = bitmapNew;                 }                 else                 {                     MyCropImage.Source = bitmap;                 }             }         }         private void BtnSave_Click(object sender, RoutedEventArgs e)         {             var dlg = new SaveFileDialog();             dlg.FileName = $"WPFDevelopers_CropImage_{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg";             dlg.DefaultExt = ".jpg";             dlg.Filter = "image file|*.jpg";             if (dlg.ShowDialog() == true)             {                 var pngEncoder = new PngBitmapEncoder();                 pngEncoder.Frames.Add(BitmapFrame.Create((BitmapSource)MyCropImage.CurrentAreaBitmap));                 using (var fs = File.OpenWrite(dlg.FileName))                 {                     pngEncoder.Save(fs);                     fs.Dispose();                     fs.Close();                 }             }         }     } }

效果图

以上就是基于WPF实现裁剪图像功能的详细内容,更多关于WPF裁剪图像的资料请关注软件开发网其它相关文章!



wpf

需要 登录 后方可回复, 如果你还没有账号请 注册新账号