WPF image avatar free cutter real-time screenshot detail amplifier

This article refers to the blog post: WPF custom picture cutter - avatar cutting (extension and improvement, real-time screenshot)

I searched for a long time on the Internet but couldn’t find a suitable screenshot frame, so I can only use WPF custom image cutter - avatar cutting (extended and perfected, real-time screenshot)_孤夜一点星的博客-CSDN Blog_wpf Image cropping I tried this article, but the result didn’t meet my needs, so I modified it based on this, thank you for the original author’s contribution

Show results

The display picture is very stuck, but it is actually very smooth

Similarities and differences with the original author

1. Change the square frame to a variable frame, which can be stretched (the size of the frame is not checked here, and the width and height of the frame may be stretched to 0 or negative. I know there is a bug, but I am too lazy to change it)

2. Checking is done when dragging, and the frame will not be dragged out of the picture

3. Optimized some algorithms to make calculations more efficient

4. Some other optimizations (such as setting the frame size when loading, calculating the preview image, etc.)

Code Logic Analysis

The code layout uses the Grid as the container, displays the image in the container, and fills the entire Grid adaptively. The selection box is actually a Border, and the selected intercepted part is actually to zoom in and out and translate the Border. There are so many layout files. The following is the code of the layout file

<UserControl x:Class="AUTOTest.Widgets.Common.ImageDealer.ImageDealerUnsafe"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="280" Background="Transparent"
             SnapsToDevicePixels="True" 
             PreviewMouseDown="UserControl_MouseDown" 
             PreviewMouseUp="UserControl_MouseUp" 
             MouseLeave="UserControl_MouseLeave" 
             PreviewMouseMove="UserControl_MouseMove"
             Loaded="onViewLoad">
    <Grid Name="MainGrid" Height="300" Width="280" Background="#DCDCDC">
        <Image Name="SoureceImage" Stretch="Uniform"></Image>
        <Border Name="ImageArea"  BorderBrush="#046BE1" BorderThickness="1,1,1,1" Panel.ZIndex="5" Margin="100" SizeChanged="ImageArea_SizeChanged">
            <Grid >
                <Rectangle Name="R_LeftUp"    Width="5" Height="5" Margin="-3" VerticalAlignment="Top"    HorizontalAlignment="Left"   Fill="White" Panel.ZIndex="0" Cursor="SizeNWSE"/>
                <Rectangle Name="R_Up"        Width="5" Height="5" Margin="-3" VerticalAlignment="Top"    HorizontalAlignment="Center" Fill="White" Panel.ZIndex="0" Cursor="SizeNS"/>
                <Rectangle Name="R_RightUp"   Width="5" Height="5" Margin="-3" VerticalAlignment="Top"    HorizontalAlignment="Right"  Fill="White" Panel.ZIndex="0" Cursor="SizeNESW"/>
                <Rectangle Name="R_Right"     Width="5" Height="5" Margin="-3" VerticalAlignment="Center" HorizontalAlignment="Right"  Fill="White" Panel.ZIndex="0" Cursor="SizeWE"/>
                <Rectangle Name="R_RightDown" Width="5" Height="5" Margin="-3" VerticalAlignment="Bottom" HorizontalAlignment="Right"  Fill="White" Panel.ZIndex="0" Cursor="SizeNWSE"/>
                <Rectangle Name="R_Down"      Width="5" Height="5" Margin="-3" VerticalAlignment="Bottom" HorizontalAlignment="Center" Fill="White" Panel.ZIndex="0" Cursor="SizeNS"/>
                <Rectangle Name="R_LeftDown"  Width="5" Height="5" Margin="-3" VerticalAlignment="Bottom" HorizontalAlignment="Left"   Fill="White" Panel.ZIndex="0" Cursor="SizeNESW"/>
                <Rectangle Name="R_Left"      Width="5" Height="5" Margin="-3" VerticalAlignment="Center" HorizontalAlignment="Left"   Fill="White" Panel.ZIndex="0" Cursor="SizeWE"/>
            </Grid>
        </Border>

    </Grid>
</UserControl>

After the layout is written, let’s see how to control the layout file. First, consider the size of the zoom marquee. When the mouse drags a selection point, the X, Y or Width, Height of the Border will be recalculated according to the selection point.

/// <summary> 计算区域圆点及宽高 </summary>
/// <param name="MouseButtonLocate">鼠标相对背景MainGrid位置</param>
/// <param name="IsRectangle">是否正方形</param>
/// <returns>NULL 或 具体值</returns>
private RectangleAreaModel CalculatedArea(Point MouseButtonLocate, bool IsRectangle, Point Locate)
{
	//边框宽度
	double BorderWidth = this.BorderWidth;
	//整体宽度
	double RectWidth = ImageArea.ActualWidth;
	//整体高度
	double RectHeight = ImageArea.ActualHeight;

	//裁剪区域
	Point OriginalPoint = new Point(0, 0);//圆点坐标
	Point TheoryPoint = new Point(0, 0);  //理论坐标
	double TheoryWidth = 0;   //理论宽度
	double TheoryHeight = 0;  //理论高度
	switch (MouseLocation)
	{
		case MouseLocationEnum.Left:
			{
				Cursor = Cursors.SizeWE;
				TheoryWidth = RectWidth + (Locate.X - MouseButtonLocate.X);
				TheoryHeight = RectHeight;
				TheoryPoint = new Point(MouseButtonLocate.X, Locate.Y);
			}
			break;
		case MouseLocationEnum.LeftUp:
			{
				Cursor = Cursors.SizeNWSE;
				TheoryWidth = RectWidth + (Locate.X - MouseButtonLocate.X);
				TheoryHeight = RectHeight + (Locate.Y - MouseButtonLocate.Y);
				TheoryPoint = new Point(MouseButtonLocate.X, MouseButtonLocate.Y);
			}
			break;
		case MouseLocationEnum.Up:
			{
				Cursor = Cursors.SizeNS;
				TheoryWidth = RectWidth;
				TheoryHeight = RectHeight + (Locate.Y - MouseButtonLocate.Y);
				TheoryPoint = new Point(Locate.X, MouseButtonLocate.Y);
			}
			break;
		case MouseLocationEnum.RightUp:
			{
				Cursor = Cursors.SizeNESW;
				TheoryWidth = MouseButtonLocate.X - Locate.X;
				TheoryHeight = RectHeight + (Locate.Y - MouseButtonLocate.Y);
				TheoryPoint = new Point(Locate.X, MouseButtonLocate.Y);
			}
			break;
		case MouseLocationEnum.Right:
			{
				Cursor = Cursors.SizeWE;
				TheoryWidth = MouseButtonLocate.X - Locate.X;
				TheoryHeight = RectHeight;
				TheoryPoint = new Point(Locate.X, Locate.Y);
			}
			break;
		case MouseLocationEnum.RightDown:
			{
				Cursor = Cursors.SizeNWSE;
				TheoryHeight = MouseButtonLocate.Y - Locate.Y;
				TheoryWidth = MouseButtonLocate.X - Locate.X;
				TheoryPoint = new Point(Locate.X, Locate.Y);
			}
			break;
		case MouseLocationEnum.Down:
			{
				Cursor = Cursors.SizeNS;
				TheoryHeight = MouseButtonLocate.Y - Locate.Y;
				TheoryWidth = RectWidth;
				TheoryPoint = new Point(Locate.X, Locate.Y);
			}
			break;
		case MouseLocationEnum.LeftDown:
			{
				Cursor = Cursors.SizeNESW;
				TheoryWidth = RectWidth + (Locate.X - MouseButtonLocate.X);
				TheoryHeight = MouseButtonLocate.Y - Locate.Y;
				TheoryPoint = new Point(MouseButtonLocate.X, Locate.Y);
			}
			break;
		default:
			return null;
	}
	return new RectangleAreaModel()
	{
		X = TheoryPoint.X,
		Y = TheoryPoint.Y,
		Width = TheoryWidth,
		Height = TheoryHeight
	};
}

Next, we consider the logic of overall panning. Panning only changes X and Y, and the width and height of the marquee remain unchanged, so the logic is relatively simple. How much the mouse’s X, Y offsets, and how much our borders offset. Of course, we can’t ignore the boundary calculation. After exceeding the boundary, we still can’t drag. The following is the code when dragging

else if (Action == MouseActionEx.DragMove)//拖动
{
	double Left = MouseDownLocate.X + (MousePoint.X - MouseDownPoint.X);
	double Top = MouseDownLocate.Y + (MousePoint.Y - MouseDownPoint.Y);
	//不能超出边界区域 超出后纠正
	// 左边距
	double leftMargin = MaxMargin + (MainGrid.ActualWidth - SoureceImage.ActualWidth) / 2;
	// 上边距
	double topMargin = MaxMargin + (MainGrid.ActualHeight - SoureceImage.ActualHeight) / 2;
	// 右边距
	double rightMargin = SourceImagePoint.X + SoureceImage.ActualWidth;
	// 下边距
	double bottomMargin = SourceImagePoint.Y + SoureceImage.ActualHeight;
	// 选择框加边框宽
	double selectAreaWidth = ImageArea.ActualWidth + MaxMargin;
	// 选择框加边框高
	double selectAreaHeight = ImageArea.ActualHeight + MaxMargin;
	if (Left < leftMargin)
		Left = leftMargin;                
	if (Top < topMargin)                
		Top = topMargin;                
	if (Left + selectAreaWidth > rightMargin)                
		Left = rightMargin - selectAreaWidth;               
	if (Top + selectAreaHeight > bottomMargin)                
		Top = bottomMargin - selectAreaHeight;
	ImageArea.Width = ImageArea.ActualWidth;
	ImageArea.Height = ImageArea.ActualHeight;

	ImageArea.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Left);
	ImageArea.SetValue(VerticalAlignmentProperty, VerticalAlignment.Top);
	ImageArea.SetValue(MarginProperty, new Thickness(Left, Top, 0, 0));

	CutImage();
}

The main problem is solved, our function is basically completed, and the rest is to take screenshots in proportion. The previous author applied a layer of UI, so I didn’t change much, and I also applied a layer. We only need to call that layer. Let’s take a look at the layer of UI interface that is applied.

<UserControl x:Class="AUTOTest.Widgets.Common.ImageDealer.ImageDealer"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="280" Background="Transparent"
             SnapsToDevicePixels="True"
             Loaded="UserControl_Loaded">
    <Grid Name="MainGrid" MinHeight="300" MinWidth="280" />

</UserControl>

I modified the logic of the UI. The previous one was too complicated. I can’t use it or understand it. Now there is an interface for screenshots and an interface for setting pictures, which means that pictures can be replaced. The overall code is very simple. You can know what it does by looking at the method name. If you want to expand, you can expand it in this class.

    public partial class ImageDealer : UserControl
    {
        public static readonly RoutedEvent OnCutImagingEventHandler = EventManager.RegisterRoutedEvent("OnCutImaging", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ImageDealer));

        #region 私有字段
        /// <summary> 截图控件 </summary>
        private ImageDealerUnsafe _ImageDealerControl = new ImageDealerUnsafe();
        /// <summary> 图片源 </summary>
        private BitmapImage _BitSource;
        #endregion

        #region 公共字段

        /// <summary> 图片源 </summary>
        public BitmapImage BitSource
        {
            get { return _BitSource; }
            set
            {
                _BitSource = value;
                _ImageDealerControl.BitSource = value;
            }
        }

        /// <summary> 截图事件 </summary>
        public event RoutedEventHandler OnCutImaging
        {
            add { AddHandler(OnCutImagingEventHandler, value); }
            remove { RemoveHandler(OnCutImagingEventHandler, value); }
        }

        #endregion

        #region ==方法==

        public ImageDealer()
        {
            InitializeComponent();
            _ImageDealerControl.OnCutImage += OnCutImage;
        }

        /// <summary> 手动设置范围 </summary>
        public void setImageAreaParemater(double x, double y, double width, double height)
        {
            _ImageDealerControl.setImageAreaParemater(x,y,width,height);
        }


        //外部截图
        public void CutImage()
        {
            if (IsLoaded == true || _ImageDealerControl == null)
                _ImageDealerControl.CutImage();
            else
                throw new Exception("尚未创建视图时无法截图!");
        }

        //截图回调
        private void OnCutImage(CutImgInfo cutImgInfo)
        {
            // BitmapSource bit
            RaiseEvent(new RoutedEventArgs(OnCutImagingEventHandler, cutImgInfo));
        }

        //加载完成
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            if (MainGrid.Children.Contains(_ImageDealerControl) == false)
                MainGrid.Children.Add(_ImageDealerControl);
        } 
 
        #endregion
    }

In fact, our function is very simple, but it is annoying to have to write it yourself when using it. I will record it to prevent it from being used in the future, or my friends will use it. I also uploaded the code, and friends who need it can download it.

Code address: https://download.csdn.net/download/baoolong/86608595

Guess you like

Origin blog.csdn.net/baoolong/article/details/126747435