WPF 实现裁剪图像
框架使用.NET4 至 .NET6
Visual Studio 2022
使用 Canvas
展示选择的裁剪图片
使用 4
个 Rectangle
设置未选中区域分别是左上右下
中间展示当前的裁剪区域使用了 Border
移动
- 左右移动使用
Canvas.SetLeft
- 上下移动使用
Canvas.SetTop
Border
获取裁剪区域获取GetLeft
、GetTop
、Border
的Width
与Height
拉伸 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裁剪图像的资料请关注其它相关文章!