?制作一個用戶頭像選擇器仿 WeGame
CropAvatar
作者:WPFDevelopersOrg - 驚鏵
原文鏈接:https://github.com/WPFDevelopersOrg/WPFDevelopers
框架使用
.NET40
;Visual Studio 2019
;制作一個用戶頭像選擇
Canvas
為父控件所實現,展示圖片使用Image
,Path
當作上方的蒙版;Canvas
:主要用途方便移動Image
,設置ClipToBounds="True"
裁剪為一個正方形200x200
做為主要展示區域;Image
:展示需要裁剪的圖片;

Path
:CombinedGeometry[1]繪制蒙版大小200x200
效果如下;

當選擇一個本地圖片的時候判斷寬與高誰更大,誰小就將它更改為
200
,另一邊做等比縮放后給到DrawingVisual
繪制一個新的BitmapFrame[2]給Image
控件做展示;當移動圖片的時候右側展示當前區域使用CroppedBitmap[3]進行裁剪并顯示;
源碼Github[4]Gitee[5]
1)
CropAvatar.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?TargetType="controls:CropAvatar"?BasedOn="{StaticResource?ControlBasicStyle}"><Setter?Property="Template"><Setter.Value><ControlTemplate?TargetType="{x:Type?controls:CropAvatar}"><Canvas?x:Name="PART_Canvas"?ClipToBounds="True"><Image?x:Name="PART_Image"?Cursor="SizeAll"?></Image><Path?x:Name="PART_Layout"?Fill="{DynamicResource?BlackSolidColorBrush}"?Width="200"?Height="200"?Opacity=".5"><Path.Data><CombinedGeometry?GeometryCombineMode="Xor"><CombinedGeometry.Geometry1><RectangleGeometry?Rect="0,0,200,200"/></CombinedGeometry.Geometry1><CombinedGeometry.Geometry2><EllipseGeometry?Center="100,100"?RadiusX="100"?RadiusY="100"/></CombinedGeometry.Geometry2></CombinedGeometry></Path.Data></Path><Grid?x:Name="PART_Grid"?Width="200"?Height="200"><Button?x:Name="PART_ReplaceButton"?Style="{StaticResource?PathButton}"HorizontalAlignment="Right"VerticalAlignment="Top"Width="40"?Height="40"?ToolTip="更換圖片"Visibility="Collapsed"><Button.Content><Path?Data="{StaticResource?PathReplace}"Fill="{StaticResource?PrimaryNormalSolidColorBrush}"Height="15"Width="15"Stretch="Fill"?/></Button.Content></Button><Button?x:Name="PART_AddButton"?Style="{StaticResource?PathButton}"Width="40"?Height="40"?ToolTip="選擇圖片"><Button.Content><Path?Data="{StaticResource?PathAdd}"Fill="{StaticResource?PrimaryNormalSolidColorBrush}"Height="20"Width="20"Stretch="Fill"?RenderTransformOrigin="0.5,0.5"?IsHitTestVisible="False"><Path.RenderTransform><RotateTransform?Angle="45"/></Path.RenderTransform></Path></Button.Content></Button></Grid></Canvas></ControlTemplate></Setter.Value></Setter></Style></ResourceDictionary>
2)CropAvatar.cs
代碼如下;
using?System;
using?System.Windows;
using?System.Windows.Controls;
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?=?ImageTemplateName,?Type?=?typeof(Image))][TemplatePart(Name?=?PathTemplateName,?Type?=?typeof(Path))][TemplatePart(Name?=?GridTemplateName,?Type?=?typeof(Grid))][TemplatePart(Name?=?ReplaceButtonTemplateName,?Type?=?typeof(Button))][TemplatePart(Name?=?AddButtonTemplateName,?Type?=?typeof(Button))]public?partial?class?CropAvatar?:?Control{private?const?string?CanvasTemplateName?=?"PART_Canvas";private?const?string?ImageTemplateName?=?"PART_Image";private?const?string?PathTemplateName?=?"PART_Layout";private?const?string?GridTemplateName?=?"PART_Grid";private?const?string?ReplaceButtonTemplateName?=?"PART_ReplaceButton";private?const?string?AddButtonTemplateName?=?"PART_AddButton";private?Point?point;private?const?int?_size?=?200;private?bool?isDown;private?bool?isLeft;private?CroppedBitmap?crop;private?Canvas?canvas;private?Image?image;private?Path?path;private?Grid?grid;private?Button?replaceButton,?addButton;private?int?initialX,?initialY,?voffsetX,?voffsetY;private?double?vNewStartX,?vNewStartY,?_StartX,?_StartY,?centerX,?centerY;private?BitmapFrame?bitmapFrame;public?ImageSource?OutImageSource{get?{?return?(ImageSource)GetValue(OutImageSourceProperty);?}set?{?SetValue(OutImageSourceProperty,?value);?}}public?static?readonly?DependencyProperty?OutImageSourceProperty?=DependencyProperty.Register("OutImageSource",?typeof(ImageSource),?typeof(CropAvatar),?new?PropertyMetadata(null));static?CropAvatar(){DefaultStyleKeyProperty.OverrideMetadata(typeof(CropAvatar),?new?FrameworkPropertyMetadata(typeof(CropAvatar)));}public?override?void?OnApplyTemplate(){base.OnApplyTemplate();canvas?=?GetTemplateChild(CanvasTemplateName)?as?Canvas;canvas.Loaded?+=?Canvas_Loaded;grid?=?GetTemplateChild(GridTemplateName)?as?Grid;image?=?GetTemplateChild(ImageTemplateName)?as?Image;image.MouseDown?+=?Image_MouseDown;image.MouseMove?+=?Image_MouseMove;image.MouseUp?+=?Image_MouseUp;image.MouseLeave?+=?Image_MouseLeave;path?=?GetTemplateChild(PathTemplateName)?as?Path;replaceButton?=?GetTemplateChild(ReplaceButtonTemplateName)?as?Button;replaceButton.Click?+=?ReplaceButton_Click;addButton?=?GetTemplateChild(AddButtonTemplateName)?as?Button;addButton.Click?+=?AddButton_Click;}private?void?Canvas_Loaded(object?sender,?RoutedEventArgs?e){if?(sender?is?Canvas?canvas){var?width?=?canvas.ActualWidth;var?height?=?canvas.ActualHeight;centerX?=?(width?-?path.Width)?/?2.0d;centerY?=?(height?-?path.Height)?/?2.0d;canvas.Clip?=?new?RectangleGeometry(new?Rect(centerX,?centerY,?200,?200));?Canvas.SetLeft(path,?centerX);Canvas.SetTop(path,?centerY);Canvas.SetLeft(grid,?centerX);Canvas.SetTop(grid,?centerY);}}private?void?Image_MouseLeave(object?sender,?MouseEventArgs?e){isDown?=?false;if?(isLeft)_StartX?=?Canvas.GetLeft(image);else_StartY?=?Canvas.GetTop(image);}private?void?Image_MouseUp(object?sender,?MouseButtonEventArgs?e){if?(isDown){var?vPoint?=?e.GetPosition(this);if?(isLeft){_StartX?=?Canvas.GetLeft(image);initialX?=?voffsetX;}else{_StartY?=?Canvas.GetTop(image);initialY?=?voffsetY;}}}private?void?Image_MouseMove(object?sender,?MouseEventArgs?e){if?(e.LeftButton?==?MouseButtonState.Pressed?&&?isDown){var?vPoint?=?e.GetPosition(this);if?(isLeft){var?voffset?=?vPoint.X?-?point.X;vNewStartX?=?_StartX?+?voffset;var?xPath?=?Canvas.GetLeft(path);if?(vNewStartX?<=?xPath?&&?vNewStartX?>=?-(bitmapFrame.Width?-?200?-?xPath)){Canvas.SetLeft(image,?vNewStartX);voffsetX?=?initialX?-?(int)voffset;voffsetX?=?voffsetX?<?0???0?:?voffsetX;crop?=?new?CroppedBitmap(bitmapFrame,?new?Int32Rect(voffsetX,?0,?_size,?_size));}}else{var?voffset?=?vPoint.Y?-?point.Y;vNewStartY?=?_StartY?+?voffset;var?yPath?=?Canvas.GetTop(path);if?(vNewStartY?<=?yPath?&&?vNewStartY?>=?-(bitmapFrame.Height?-?200?-?yPath)){Canvas.SetTop(image,?vNewStartY);voffsetY?=?initialY?-?(int)voffset;voffsetY?=?voffsetY?<?0???0?:?voffsetY;crop?=?new?CroppedBitmap(bitmapFrame,?new?Int32Rect(0,?voffsetY,?_size,?_size));}}OutImageSource?=?crop;}}private?void?Image_MouseDown(object?sender,?MouseButtonEventArgs?e){isDown?=?true;point?=?e.GetPosition(this);}private?void?ReplaceButton_Click(object?sender,?RoutedEventArgs?e){InitialImage();}private?void?AddButton_Click(object?sender,?RoutedEventArgs?e){InitialImage();}void?InitialImage(){vNewStartX?=?0;vNewStartY?=?0;var?uri?=?ControlsHelper.ImageUri();if?(uri?==?null)?return;var?bitmap?=?new?BitmapImage(uri);if?(bitmap.Height?>?bitmap.Width){double?scale?=?(double)bitmap.Width?/?(double)path.Width;image.Width?=?_size;image.Height?=?(double)bitmap.Height?/?scale;isLeft?=?false;}else?if?(bitmap.Width?>?bitmap.Height){double?scale?=?(double)bitmap.Height?/?(double)path.Height;image.Width?=?(double)bitmap.Width?/?scale;image.Height?=?_size;isLeft?=?true;}bitmapFrame?=?ControlsHelper.CreateResizedImage(bitmap,?(int)image.Width,?(int)image.Height,?0);image.Source?=?bitmapFrame;if?(image.Source?!=?null){replaceButton.Visibility?=?Visibility.Visible;addButton.Visibility?=?Visibility.Collapsed;}Canvas.SetLeft(grid,?centerX);Canvas.SetTop(grid,?centerY);_StartX?=?(canvas.ActualWidth?-?image.Width)?/?2.0d;_StartY?=?(canvas.ActualHeight?-?image.Height)?/?2.0d;Canvas.SetLeft(image,?_StartX);Canvas.SetTop(image,?_StartY);????????if?(isLeft){initialX?=?(int)(image.Width?-?200)?/?2;initialY?=?0;crop?=?new?CroppedBitmap(bitmapFrame,?new?Int32Rect(initialX,?0,?_size,?_size));}else{initialY?=?(int)(image.Height?-?200)?/?2;initialX?=?0;crop?=?new?CroppedBitmap(bitmapFrame,?new?Int32Rect(0,?initialY,?_size,?_size));}OutImageSource?=?crop;}}
}
3)CropAvatarWindow.xaml
使用如下;
<ws:Window?x:Class="WPFDevelopers.Samples.ExampleViews.CropAvatarWindow"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:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"xmlns:ws="https://github.com/WPFDevelopersOrg.WPFDevelopers.Minimal"mc:Ignorable="d"??WindowStyle="ToolWindow"?ResizeMode="NoResize"WindowStartupLocation="CenterScreen"Title="WPF?開發者-頭像選擇器"?Height="450"?Width="800"><Grid><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition/><RowDefinition?Height="Auto"/></Grid.RowDefinitions><wpfdev:CropAvatar?x:Name="MyCropAvatar"/><Image?Grid.Column="1"?Name="CropAvatarImage"?Source="{Binding?ElementName=MyCropAvatar,Path=OutImageSource}"?Stretch="Fill"?Width="200"?Height="200"><Image.Clip><EllipseGeometry?Center="100,100"?RadiusX="100"?RadiusY="100"/></Image.Clip></Image><UniformGrid?Grid.Row="1"?Grid.ColumnSpan="2"?HorizontalAlignment="Center"?VerticalAlignment="Center"><Button??Content="保存"?Click="btnSave_Click"?Style="{StaticResource?PrimaryButton}"?Margin="4,0"/><Button??Content="關閉"?Click="btnClose_Click"?Margin="4,0"/></UniformGrid></Grid>
</ws:Window>
4) CropAvatarWindow.xaml.cs
代碼如下;
using?System.Windows;namespace?WPFDevelopers.Samples.ExampleViews
{///?<summary>///?CropAvatarWindow.xaml?的交互邏輯///?</summary>public?partial?class?CropAvatarWindow?{public?CropAvatarWindow(){InitializeComponent();}private?void?btnSave_Click(object?sender,?RoutedEventArgs?e){DialogResult?=?true;}private?void?btnClose_Click(object?sender,?RoutedEventArgs?e){DialogResult?=?false;}}
}
5) CropAvatarExample.xaml
使用如下;
<UserControl?x:Class="WPFDevelopers.Samples.ExampleViews.CropAvatarExample"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"?xmlns:d="http://schemas.microsoft.com/expression/blend/2008"?xmlns:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"mc:Ignorable="d"?d:DesignHeight="450"?d:DesignWidth="800"><Grid><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions><Button?Content="圖像選擇器"?VerticalAlignment="Center"?HorizontalAlignment="Center"?Click="Button_Click"/><Image?Grid.Column="1"?Name="MyImage"Stretch="Fill"?Width="200"?Height="200"><Image.Clip><EllipseGeometry?Center="100,100"?RadiusX="100"?RadiusY="100"/></Image.Clip></Image></Grid>
</UserControl>
6) CropAvatarExample.xaml.cs
代碼如下;
using?System.Windows.Controls;namespace?WPFDevelopers.Samples.ExampleViews
{///?<summary>///?CropAvatarExample.xaml?的交互邏輯///?</summary>public?partial?class?CropAvatarExample?:?UserControl{public?CropAvatarExample(){InitializeComponent();}private?void?Button_Click(object?sender,?System.Windows.RoutedEventArgs?e){var?cropAvatarWindow?=?new?CropAvatarWindow();if?(cropAvatarWindow.ShowDialog()?==?true){MyImage.Source?=?cropAvatarWindow.CropAvatarImage.Source;}}}
}
參考資料[1]
CombinedGeometry: https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.combinedgeometry?view=netframework-4.0
[2]BitmapFrame: https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.imaging.bitmapframe?view=windowsdesktop-6.0
[3]CroppedBitmap: https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.imaging.croppedbitmap?view=windowsdesktop-6.0
[4]Github: https://github.com/WPFDevelopersOrg/WPFDevelopers
[5]Gitee: https://gitee.com/WPFDevelopersOrg/WPFDevelopers