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 |