-
框架使用
.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();
}
}
}
}
}
