WPF 实现裁剪图像

325 天前
 yanjinhua

WPF 实现裁剪图像

控件名:CropImage

作 者:WPFDevelopersOrg - 驚鏵

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

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 代码如下:

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

1299 次点击
所在节点    .NET
6 条回复
vitovan
325 天前
很好的项目,谢谢分享。

请问现在 WPF 主要用在哪方面呢? Windows 桌面应用吗?可不可以跨平台?
OutOfMemoryError
325 天前
请问一下主界面的 ui 是哪套框架的?
magicyao
324 天前
收藏了,谢谢分享
yanjinhua
324 天前
yanjinhua
324 天前
@magicyao 相互学习
yanjinhua
324 天前
@vitovan 不客气,相互学习。
WPF 主要那些方面呢?答:金融、GIS 、教育、医疗。
Windows 桌面应用吗?答:是的
可不可以跨平台? 答:不可以。
XAML 跨平台语法可以看 Avalonia

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/950084

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX