WPF 实现裁剪图像
控件名:CropImage
作 者:WPFDevelopersOrg -
框架使用.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)); } } }
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(); } } } } }
![]() | 1 vitovan 2023-06-19 18:26:41 +08:00 很好的项目,谢谢分享。 请问现在 WPF 主要用在哪方面呢? Windows 桌面应用吗?可不可以跨平台? |
![]() | 2 OutOfMemoryError 2023-06-19 18:34:06 +08:00 请问一下主界面的 ui 是哪套框架的? |
![]() | 3 magicyao 2023-06-20 11:50:54 +08:00 收藏了,谢谢分享 |
![]() | 4 yanjinhua OP |
![]() | 5 yanjinhua OP |