基于WPF实现裁剪图像功能

来自:网络
时间:2023-07-24
阅读:

WPF 实现裁剪图像

框架使用.NET4 至 .NET6

Visual Studio 2022

基于WPF实现裁剪图像功能

使用 Canvas 展示选择的裁剪图片

使用 4 个 Rectangle 设置未选中区域分别是左上右下

中间展示当前的裁剪区域使用了 Border 移动

  • 左右移动使用 Canvas.SetLeft
  • 上下移动使用 Canvas.SetTop
  • Border 获取裁剪区域获取GetLeftGetTopBorderWidthHeight

拉伸 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();
                }
            }
        }
    }
}

效果图

基于WPF实现裁剪图像功能

基于WPF实现裁剪图像功能

以上就是基于WPF实现裁剪图像功能的详细内容,更多关于WPF裁剪图像的资料请关注其它相关文章!

返回顶部
顶部