V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yanjinhua
V2EX  ›  .NET

WPF 实现裁剪图像

  •  1
     
  •   yanjinhua · 2023-06-19 18:24:22 +08:00 · 1562 次点击
    这是一个创建于 560 天前的主题,其中的信息可能已经有所发展或是发生改变。

    WPF 实现裁剪图像

    控件名:CropImage

    作 者:WPFDevelopersOrg - 驚鏵

    原文链接https://github.com/WPFDevelopersOrg/WPFDevelopers

    • 框架使用.NET4 至 .NET6

    • Visual Studio 2022

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

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

    • 中间展示当前的裁剪区域使用了 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));
                
            }
        }
    }
    

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


    gitee

    6 条回复    2023-06-20 16:54:32 +08:00
    vitovan
        1
    vitovan  
       2023-06-19 18:26:41 +08:00
    很好的项目,谢谢分享。

    请问现在 WPF 主要用在哪方面呢? Windows 桌面应用吗?可不可以跨平台?
    OutOfMemoryError
        2
    OutOfMemoryError  
       2023-06-19 18:34:06 +08:00
    请问一下主界面的 ui 是哪套框架的?
    magicyao
        3
    magicyao  
       2023-06-20 11:50:54 +08:00
    收藏了,谢谢分享
    yanjinhua
        5
    yanjinhua  
    OP
       2023-06-20 16:52:59 +08:00
    @magicyao 相互学习
    yanjinhua
        6
    yanjinhua  
    OP
       2023-06-20 16:54:32 +08:00   ❤️ 1
    @vitovan 不客气,相互学习。
    WPF 主要那些方面呢?答:金融、GIS 、教育、医疗。
    Windows 桌面应用吗?答:是的
    可不可以跨平台? 答:不可以。
    XAML 跨平台语法可以看 Avalonia
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1074 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 22:41 · PVG 06:41 · LAX 14:41 · JFK 17:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.