如何实现图像Masked Blur效果:保留指定清晰区域并添加边框
实现局部清晰+全局模糊的可行思路
嘿,我之前刚好做过类似的需求,踩过一些坑,给你几个亲测可行的实现思路,应该能解决你的问题:
思路一:Stack + ClipPath 组合(最直观易上手)
这个思路的核心是分层控制显示区域:底层放全图模糊效果,上层只显示清晰图的ROI区域,最后叠加边框。完全避开了你之前遇到的“模糊图覆盖清晰区域”的问题。
具体步骤:
- 第一层:放模糊处理后的完整图像
- 第二层:放原始清晰图像,用
ClipPath裁剪出你需要的感兴趣区域(ROI),这样只有ROI部分会显示清晰图,其他区域会被裁剪掉,露出底层的模糊图 - 第三层:在ROI的位置放一个带边框的容器,实现高亮效果
代码示例(Flutter环境):
Stack( children: [ // 底层:全图模糊 ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Image.asset('assets/your_image.jpg'), ), // 中层:裁剪出清晰的ROI ClipPath( clipper: _ROIClipper(roiRect: Rect.fromLTWH(100, 100, 200, 200)), child: Image.asset('assets/your_image.jpg'), ), // 顶层:ROI边框 Positioned.fromRect( rect: Rect.fromLTWH(100, 100, 200, 200), child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.redAccent, width: 2), ), ), ), ], ) // 自定义裁剪器,只保留ROI区域 class _ROIClipper extends CustomClipper<Path> { final Rect roiRect; _ROIClipper({required this.roiRect}); @override Path getClip(Size size) { final path = Path(); path.addRect(roiRect); return path; } @override bool shouldReclip(covariant _ROIClipper oldClipper) { return oldClipper.roiRect != roiRect; } }
思路二:ExcludeClipPath 反向控制(更高效的图层复用)
如果不想重复加载两次图像,这个思路更合适:只加载一次清晰图,然后用ExcludeClipPath让模糊效果避开ROI区域。
原理:
- 底层放原始清晰图
- 上层放模糊后的图像,但用
ExcludeClipPath排除掉ROI区域,这样ROI部分不会被模糊图覆盖,依然显示清晰的底层图像 - 最后叠加边框
代码示例:
Stack( children: [ // 底层:原始清晰图 Image.asset('assets/your_image.jpg'), // 上层:模糊图,但排除ROI区域 ExcludeClipPath( clipPath: _ROIClipper(roiRect: Rect.fromLTWH(100, 100, 200, 200)), child: ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: Container( width: double.infinity, height: double.infinity, color: Colors.transparent, child: Image.asset('assets/your_image.jpg', fit: BoxFit.fill), ), ), ), // ROI边框 Positioned.fromRect( rect: Rect.fromLTWH(100, 100, 200, 200), child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.blueAccent, width: 2), ), ), ), ], )
思路三:CustomPainter 直接绘制(最灵活的方案)
你之前尝试CustomPainter没成功,大概率是绘制顺序或者模糊效果的应用方式不对。这个方案直接在画布上控制每一步的绘制,适合需要动态调整ROI或者模糊参数的场景。
核心步骤:
- 先绘制整个模糊后的图像
- 再在ROI位置绘制原始清晰的图像区域
- 最后绘制ROI的边框
代码示例:
// 注意:需要先通过ImageProvider获取ui.Image对象 CustomPaint( painter: _ROIBlurPainter( image: _loadedImage, // 预加载好的ui.Image roiRect: Rect.fromLTWH(100, 100, 200, 200), blurSigma: 5, ), child: SizedBox( width: _loadedImage.width.toDouble(), height: _loadedImage.height.toDouble(), ), ) class _ROIBlurPainter extends CustomPainter { final ui.Image image; final Rect roiRect; final double blurSigma; _ROIBlurPainter({ required this.image, required this.roiRect, required this.blurSigma, }); @override void paint(Canvas canvas, Size size) { // 1. 绘制全图模糊效果 final blurPaint = Paint() ..imageFilter = ImageFilter.blur(sigmaX: blurSigma, sigmaY: blurSigma); canvas.drawImage(image, Offset.zero, blurPaint); // 2. 绘制清晰的ROI区域 final srcRect = roiRect; // 假设图像尺寸和画布一致,若不一致需做坐标转换 final dstRect = roiRect; canvas.drawImageRect(image, srcRect, dstRect, Paint()); // 3. 绘制ROI边框 final borderPaint = Paint() ..color = Colors.greenAccent ..style = PaintingStyle.stroke ..strokeWidth = 2; canvas.drawRect(roiRect, borderPaint); } @override bool shouldRepaint(covariant _ROIBlurPainter oldDelegate) { return oldDelegate.image != image || oldDelegate.roiRect != roiRect || oldDelegate.blurSigma != blurSigma; } }
为什么你之前的叠加方法不可行?
直接把清晰图叠在模糊图上的话,清晰图会覆盖整个画布,包括非ROI区域,所以看不到模糊效果。而上面的思路都是通过裁剪/排除区域的方式,让清晰图只在ROI区域显示,非ROI区域露出模糊图,完美解决了覆盖问题。
内容的提问来源于stack exchange,提问作者Jens
相关产品推荐
相关产品推荐

