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(0, 0, _canvas.ActualWidth, _canvas.ActualHeight));
_imageSnapshot = new RenderTargetBitmap(
(int)_canvas.ActualWidth,
(int)_canvas.ActualHeight,
96, 96, PixelFormats.Pbgra32);
_imageSnapshot.Render(_canvas);
}
3. 马赛克绘制
-
DrawMosaicBlock:center点、mosaic块大小和brush大小作为参数。它会在指定区域内绘制多个马赛克块,调用GetAreaAverageColor来获取每个块的颜色。
private void DrawMosaicBlock(Point center, int blockSize, int brushSize)
{
if (_imageSnapshot == null) return;
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, 1, 1), pixels, 4, 0);
totalR += pixels[2];
totalG += pixels[1];
totalB += pixels[0];
count++;
}
}
}
if (count == 0) return 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 == 0) return;
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 == 0) return;
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);
}
}
}
原文链接: 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

