大数跨境
0
0

WPF 截图控件(十):马赛克效果

WPF 截图控件(十):马赛克效果 dotNET跨平台
2026-01-05
10
导读:WPF 截图控件(十):马赛克效果标 题:WPF 截图控件(十):马赛克效果作 者:WPFDevel

WPF 截图控件(十):马赛克效果

标   题:WPF 截图控件(十):马赛克效果

作   者:WPFDevelopersOrg - 驚鏵

距离截图控件上次修改已经过去了 3 年多,这次更新新增了截图控件的马赛克功能。

接着上一篇

1. 新增枚举
public enum ScreenCutMouseType
{
    DrawMosaic
}
2. 快照生成
  • TakeSnapshot:生成当前画布的快照。
private void TakeSnapshot()
{
    _canvas.Measure(new System.Windows.Size(_canvas.ActualWidth, _canvas.ActualHeight));
    _canvas.Arrange(new Rect(00, _canvas.ActualWidth, _canvas.ActualHeight));

    _imageSnapshot = new RenderTargetBitmap(
        (int)_canvas.ActualWidth,
        (int)_canvas.ActualHeight,
        9696, PixelFormats.Pbgra32);

    _imageSnapshot.Render(_canvas);
}

3. 马赛克绘制
  • DrawMosaicBlock: center 点、mosaic 块大小和 brush 大小作为参数。它会在指定区域内绘制多个马赛克块,调用 GetAreaAverageColor 来获取每个块的颜色。
        private void DrawMosaicBlock(Point center, int blockSize, int brushSize)
        {
            if (_imageSnapshot == nullreturn;

            int mosaicSize = blockSize;
            int blocksPerRow = brushSize / mosaicSize;

            for (int i = 0; i < blocksPerRow; i++)
            {
                for (int j = 0; j < blocksPerRow; j++)
                {
                    double x = center.X - brushSize / 2 + i * mosaicSize;
                    double y = center.Y - brushSize / 2 + j * mosaicSize;

                    Point blockCenter = new Point(x + mosaicSize / 2, y + mosaicSize / 2);
                    Color color = GetAreaAverageColor(blockCenter, mosaicSize);

                    var block = new Rectangle
                    {
                        Width = mosaicSize,
                        Height = mosaicSize,
                        Fill = new SolidColorBrush(color),
                        IsHitTestVisible = false
                    };

                    Canvas.SetLeft(block, x);
                    Canvas.SetTop(block, y);

                   _canvas.Children.Add(block);

                    _currentStrokeRectangles.Add(block);
                }
            }
        }
4. 平均颜色计算
  • GetAreaAverageColor:循环指定区域内的每个像素,计算其总红、绿、蓝值,从而得到平均颜色。
        private Color GetAreaAverageColor(Point center, int areaSize)
        {
            try
            {
                double scaleX = _imageSnapshot.PixelWidth / _canvas.ActualWidth;
                double scaleY = _imageSnapshot.PixelHeight / _canvas.ActualHeight;
                int pixelX = (int)(center.X * scaleX);
                int pixelY = (int)(center.Y * scaleY);
                int halfSize = areaSize / 2;
                int totalR = 0, totalG = 0, totalB = 0;
                int count = 0;
                for (int dx = -halfSize; dx <= halfSize; dx++)
                {
                    for (int dy = -halfSize; dy <= halfSize; dy++)
                    {
                        int x = pixelX + dx;
                        int y = pixelY + dy;

                        if (x >= 0 && x < _imageSnapshot.PixelWidth &&
                            y >= 0 && y < _imageSnapshot.PixelHeight)
                        {
                            byte[] pixels = newbyte[4];
                            _imageSnapshot.CopyPixels(new Int32Rect(x, y, 11), pixels, 40);

                            totalR += pixels[2];
                            totalG += pixels[1];
                            totalB += pixels[0];
                            count++;
                        }
                    }
                }
                if (count == 0return Colors.Gray;
                return Color.FromRgb(
                    (byte)(totalR / count),
                    (byte)(totalG / count),
                    (byte)(totalB / count));
            }
            catch
            {
                return Colors.Gray;
            }
        }
5. 完成当前绘制
  • 在完成绘制马赛克时,先检查当前的矩形列表是否为空,然后移除临时矩形,创建一个新的绘制容器,并将其添加到画布中,同时将其推入历史记录栈。
private void CompleteCurrentStroke()
{
    if (_currentStrokeRectangles.Count == 0return;
    
    RemoveTemporaryRectangles();
    CreateStrokeContainer();
    _canvas.Children.Add(_currentStrokeContainer);
    _strokeHistory.Push(_currentStrokeContainer);
    _currentStrokeContainer = null;
    _currentStrokeRectangles.Clear();
}
6. 移除临时矩形
  • 遍历当前的矩形列表并从画布中移除它们。
private void RemoveTemporaryRectangles()
{
    foreach (var rect in _currentStrokeRectangles)
    {
        _canvas.Children.Remove(rect);
    }
}

7. 创建绘制容器
  • 计算出所有矩形的最小和最大坐标,以确定绘制容器的尺寸与位置。之后,它创建一个带有圆角的矩形几何体,并使用 CreateMosaicVisualBrush 生成填充效果。
  • 在 DrawingVisual 上绘制当前的矩形,以生成马赛克效果并返回一个 VisualBrush
private void CreateStrokeContainer()
{
    if (_currentStrokeRectangles.Count == 0return;

    double minX = double.MaxValue;
    double minY = double.MaxValue;
    double maxX = double.MinValue;
    double maxY = double.MinValue;

    foreach (var rect in _currentStrokeRectangles)
    {
        double x = Canvas.GetLeft(rect);
        double y = Canvas.GetTop(rect);

        minX = Math.Min(minX, x);
        minY = Math.Min(minY, y);
        maxX = Math.Max(maxX, x + rect.Width);
        maxY = Math.Max(maxY, y + rect.Height);
    }

    double width = maxX - minX;
    double height = maxY - minY;

    var roundedRect = CreateRoundedRectangleGeometry(width, height);

    var container = new Path
    {
        Data = roundedRect,
        IsHitTestVisible = false,
        Fill = CreateMosaicVisualBrush(minX, minY, width, height)
    };

    Canvas.SetLeft(container, minX);
    Canvas.SetTop(container, minY);

    _currentStrokeContainer = container;
}
private Brush CreateMosaicVisualBrush(double left, double top, double width, double height)
{
    var drawingVisual = new DrawingVisual();

    using (var context = drawingVisual.RenderOpen())
    {
        foreach (var rect in _currentStrokeRectangles)
        {
            double relativeX = Canvas.GetLeft(rect) - left;
            double relativeY = Canvas.GetTop(rect) - top;

            var rectGeometry = new RectangleGeometry(
                new Rect(relativeX, relativeY, rect.Width, rect.Height));

            context.DrawGeometry(rect.Fill, null, rectGeometry);
        }
    }
    
    returnnew VisualBrush(drawingVisual)
    {
        Stretch = Stretch.None,
        AlignmentX = AlignmentX.Left,
        AlignmentY = AlignmentY.Top
    };
}
8. 撤销功能
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
    if (e.KeyStates == Keyboard.GetKeyStates(Key.Z) && Keyboard.Modifiers == ModifierKeys.Control)
    {
        UndoLastStroke();
    }
}
private void UndoLastStroke()
{
    if (_strokeHistory.Count > 0)
    {
        var lastStroke = _strokeHistory.Pop();
        _canvas.Children.Remove(lastStroke);
    }
}
9. XAML 示例代码如下:
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <CheckBox
                Margin="0,10,0,10"
                HorizontalAlignment="Center"
                Content="截图时隐藏当前窗口"
                IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ScreenCutExample}}, Path=IsChecked}" />

            <Button
                Grid.Row="1"
                HorizontalAlignment="Center"
                VerticalAlignment="Top"
                Click="Button_Click"
                Content="截屏" />

            <Button
                Grid.Row="1"
                Grid.Column="1"
                HorizontalAlignment="Center"
                VerticalAlignment="Top"
                Click="ButtonExt_Click"
                Content="截屏Ext"
                Style="{StaticResource WD.SuccessPrimaryButton}" />

            <Image
                x:Name="myImage"
                Grid.Row="2"
                Grid.ColumnSpan="2"
                Stretch="Uniform" />

        </Grid>
10. CSharp 示例代码如下:
 public partialclassScreenCutExample : UserControl
    {
        publicbool IsChecked
        {
            get { return (bool)GetValue(IsCheckedProperty); }
            set { SetValue(IsCheckedProperty, value); }
        }

        publicstaticreadonly DependencyProperty IsCheckedProperty =
            DependencyProperty.Register("IsChecked"typeof(bool), typeof(ScreenCutExample), new PropertyMetadata(false));


        public ScreenCutExample()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (IsChecked)
            {
                App.CurrentMainWindow.WindowState = WindowState.Minimized;
            }
            ThreadPool.QueueUserWorkItem(state =>
            {
                Thread.Sleep(350); 
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    ScreenCapture screenCapturer = new ScreenCapture();
                    screenCapturer.SnapCompleted += ScreenCapturer_SnapCompleted;
                    screenCapturer.SnapCanceled += ScreenCapturer_SnapCanceled;
                    screenCapturer.SnapSaveFullPath += ScreenCapturer_SnapSaveFullPath;
                    screenCapturer.Capture();
                }));
            });
        }

        private void ScreenCapturer_SnapSaveFullPath(string text)
        {
            Message.Push($"截图路径:{text}", MessageBoxImage.Information);
        }

        private void ScreenCapturer_SnapCanceled()
        {
            App.CurrentMainWindow.WindowState = WindowState.Normal;
            Message.Push($"{DateTime.Now} 取消截图", MessageBoxImage.Information);
        }

        private void ScreenCapturer_SnapCompleted(System.Windows.Media.Imaging.CroppedBitmap bitmap)
        {
            myImage.Source = bitmap;
            App.CurrentMainWindow.WindowState = WindowState.Normal;
        }
        private void ButtonExt_Click(object sender, RoutedEventArgs e)
        {
            if (IsChecked)
            {
                App.CurrentMainWindow.WindowState = WindowState.Minimized;
            }

            var screenCaptureExt = new ScreenCaptureExt();
            screenCaptureExt.SnapCanceled += ScreenCaptureExt_SnapCanceled;
            screenCaptureExt.SnapCompleted += ScreenCaptureExt_SnapCompleted;
            screenCaptureExt.SnapSaveFullPath += ScreenCaptureExt_SnapSaveFullPath;
        }

        private void ScreenCaptureExt_SnapSaveFullPath(string text)
        {
            Message.Push($"截图路径:{text}", MessageBoxImage.Information);
        }

        private void ScreenCaptureExt_SnapCompleted(System.Windows.Media.Imaging.BitmapSource bitmap)
        {
            myImage.Source = bitmap;
            App.CurrentMainWindow.WindowState = WindowState.Normal;
        }

        private void ScreenCaptureExt_SnapCanceled()
        {
            try
            {
                if (App.CurrentMainWindow.WindowState == WindowState.Minimized)
                    App.CurrentMainWindow.WindowState = WindowState.Normal;
                Message.Push($"{DateTime.Now} 取消截图", MessageBoxImage.Information);
            }
            catch (Exception ex)
            {
                Message.Show(ex.Message);
            }
        }
    }



参考资料
[1] 

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

[2] 

码云链接: https://gitee.com/WPFDevelopersOrg/WPFDevelopers

[3] 

GitHub 源码地址: https://github.com/WPFDevelopersOrg/WPFDevelopers/blob/master/src/WPFDevelopers.Shared/Controls/ScreenCut/ScreenCut.cs

[4] 

Gitee 源码地址: https://gitee.com/WPFDevelopersOrg/WPFDevelopers/blob/master/src/WPFDevelopers.Shared/Controls/ScreenCut/ScreenCut.cs

【声明】内容源于网络
0
0
dotNET跨平台
专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
内容 1007
粉丝 0
dotNET跨平台 专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
总阅读17.3k
粉丝0
内容1.0k