制作一个用户头像选择器仿 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