news 2026/3/31 13:37:53

如何在图片上绘制马赛克效果

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何在图片上绘制马赛克效果

如何在图片上绘制马赛克效果

标 题:如何在图片上绘制马赛克效果

作 者:WPFDevelopersOrg -驚鏵

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

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

需求:需要在图片上绘制马赛克效果,并切可以切换显示不同的图片。

  • 通过按钮进行切换背景图片。

  • 能够在图片上绘制马赛克效果。

1. 新增MainWindow.xaml代码如下:
  • Canvas: 我们使用Canvas控件作为绘图区域,CanvasBackground用于显示切换的背景图片。

  • Buttons: 左侧和右侧的按钮用于切换背景图片。

<wd:Window x:Class="ImageMosaic.MainWindow" 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:ImageMosaic" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers" Title="Mosaic" Width="800" Height="450" mc:Ignorable="d"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Canvas x:Name="PART_Canvas" Grid.ColumnSpan="3"> <Canvas.Background> <ImageBrush x:Name="CanvasBackground" Stretch="UniformToFill" /> </Canvas.Background> </Canvas> <Button Grid.Column="0" Width="50" Height="50" wd:ElementHelper.IsRound="True" Background="#80000000" Click="PrevButton_Click" Style="{StaticResource WD.DefaultButton}"> <wd:PathIcon Kind="Previous" /> </Button> <Button Grid.Column="2" Width="50" Height="50" wd:ElementHelper.IsRound="True" Background="#80000000" Click="NextButton_Click" Style="{StaticResource WD.DefaultButton}"> <wd:PathIcon Kind="Next" /> </Button> </Grid> </wd:Window>
2. 新增MainWindow.xaml代码如下:
  • 字段定义:

    • _imageSnapshot: 用于存储当Image的快照,用于后面马赛克绘制。

    • _pointStart: 记录鼠标按下时的位置,用于绘制马赛克。

    • _backgroundImages: 存储加载的Image列表。

    • _currentImageIndex: 当前显示的Image索引。

  • 构造函数: 初始化控件,加载Image并设置事件。

  • LoadImages: 读取指定路径的Image并将其添加到_backgroundImages列表中。

  • UpdateBackground: 更新画布的Backgroup,处理图像切换,并在Canvas加载完毕后拍摄快照。

按钮事件:PrevButton_ClickNextButton_Click用于切换Image

鼠标事件: 处理鼠标点击移动释放事件,用于绘制马赛克效果。

TakeSnapshot方法: 创建当前画布的快照,以便后续绘制马赛克效果。

DrawMosaicBlock方法: 在鼠标拖动时绘制马赛克块,利用GetAreaAverageColor方法获取区域的平均颜色。

GetAreaAverageColor方法: 计算给定区域内的平均颜色,并返回相应的颜色值。

public partialclassMainWindow { private RenderTargetBitmap _imageSnapshot; private Point? _pointStart; private List<ImageSource> _backgroundImages = new List<ImageSource>(); privateint _currentImageIndex = 0; public MainWindow() { InitializeComponent(); LoadImages(); Loaded += OnMainWindow_Loaded; UpdateBackground(); } private void LoadImages() { _backgroundImages.Clear(); string[] imagePaths = { "08bf74e1e5922117c4be2f6735b078bb.jpg", "0943484gC8g.jpg", "104022Bj7Vj.jpg", "234506wB8aC.jpg", "1a374f1629daeb4dea9782d09b47d823.jpg" }; foreach (var path in imagePaths) { try { var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.UriSource = new Uri($"pack://application:,,,/{path}", UriKind.Absolute); bitmap.CacheOption = BitmapCacheOption.OnLoad; bitmap.EndInit(); _backgroundImages.Add(bitmap); } catch (Exception ex) { } } } private void UpdateBackground() { PART_Canvas.Children.Clear(); if (_backgroundImages.Count == 0) return; if (_currentImageIndex < 0) _currentImageIndex = _backgroundImages.Count - 1; if (_currentImageIndex >= _backgroundImages.Count) _currentImageIndex = 0; CanvasBackground.ImageSource = _backgroundImages[_currentImageIndex]; if (PART_Canvas.IsLoaded) TakeSnapshot(); } private void PrevButton_Click(object sender, RoutedEventArgs e) { _currentImageIndex--; UpdateBackground(); } private void NextButton_Click(object sender, RoutedEventArgs e) { _currentImageIndex++; UpdateBackground(); } private void OnMainWindow_Loaded(object sender, RoutedEventArgs e) { TakeSnapshot(); } protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnPreviewMouseLeftButtonDown(e); _pointStart = e.GetPosition(PART_Canvas); } protected override void OnPreviewMouseMove(MouseEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed && _pointStart.HasValue) { var current = e.GetPosition(PART_Canvas); if ((current - _pointStart.Value).Length < 10) return; _pointStart = current; DrawMosaicBlock(current, 10, 20); } } protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e) { _pointStart = null; } private void TakeSnapshot() { PART_Canvas.Measure(new System.Windows.Size(PART_Canvas.ActualWidth, PART_Canvas.ActualHeight)); PART_Canvas.Arrange(new Rect(0, 0, PART_Canvas.ActualWidth, PART_Canvas.ActualHeight)); _imageSnapshot = new RenderTargetBitmap( (int)PART_Canvas.ActualWidth, (int)PART_Canvas.ActualHeight, 96, 96, PixelFormats.Pbgra32); _imageSnapshot.Render(PART_Canvas); } 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); PART_Canvas.Children.Add(block); } } } private Color GetAreaAverageColor(Point center, int areaSize) { try { double scaleX = _imageSnapshot.PixelWidth / PART_Canvas.ActualWidth; double scaleY = _imageSnapshot.PixelHeight / PART_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; } }

参考资料

[1]

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

[2]

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/25 12:16:44

Anaconda Prompt替代方案:Miniconda-Python3.10终端快捷激活

Miniconda-Python3.10&#xff1a;轻量级终端环境的高效激活实践 在数据科学与AI开发日益普及的今天&#xff0c;一个常见却令人头疼的问题是&#xff1a;为什么同一个Python脚本&#xff0c;在同事电脑上运行正常&#xff0c;到了自己机器上却报错“模块未找到”或“版本不兼容…

作者头像 李华
网站建设 2026/3/27 16:13:12

STM32CubeMX安装包与IDE集成入门操作指南

从零开始搭建STM32开发环境&#xff1a;CubeMX实战入门与IDE无缝集成 你是不是也经历过这样的场景&#xff1f;刚拿到一块STM32开发板&#xff0c;满怀激情打开数据手册&#xff0c;翻到时钟树那一页——密密麻麻的PLL、分频器、倍频路径看得头晕眼花。配错了&#xff0c;系统…

作者头像 李华
网站建设 2026/3/15 8:46:43

信息安全篇---密钥生成、加密、解密

&#x1f4e6; 故事设定小红想接收秘密信件&#xff0c;她要做三件事&#xff1a;造一套魔法锁具&#xff08;生成密钥对&#xff09;把“魔法锁”发给朋友&#xff08;公布公钥&#xff09;用“魔法钥匙”开锁读信&#xff08;私钥解密&#xff09;朋友小明要给小红寄信&#…

作者头像 李华
网站建设 2026/3/26 14:52:27

DownKyi视频下载神器:B站无限下载终极指南

还在为无法离线观看B站精彩内容而烦恼吗&#xff1f;DownKyi作为专业的B站视频下载工具&#xff0c;为你提供全格式视频下载解决方案。这款开源软件支持从标准画质到8K超高清、HDR、杜比视界等高级视频格式&#xff0c;满足各种场景下的下载需求。 【免费下载链接】downkyi 哔哩…

作者头像 李华
网站建设 2026/3/13 9:59:51

LeagueAkari:英雄联盟智能助手完整使用指南

LeagueAkari&#xff1a;英雄联盟智能助手完整使用指南 【免费下载链接】LeagueAkari ✨兴趣使然的&#xff0c;功能全面的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/LeagueAkari LeagueAkari是一款基…

作者头像 李华
网站建设 2026/3/26 10:19:06

LeaguePrank深度评测:游戏数据展示工具的边界探索

在现代游戏生态中&#xff0c;游戏数据展示工具始终处于技术与道德的交叉地带。LeaguePrank作为一款基于LCUAPI的本地化定制工具&#xff0c;为《英雄联盟》玩家提供了展示层数据展示的可能性。这款工具能否在安全合规的前提下满足用户的个性化需求&#xff1f;让我们从技术解析…

作者头像 李华