Flutter中使用canvas.drawImageRect绘制圆形图片失败求助
解决Canvas绘制圆形图片的问题
你遇到的问题很常见——你的代码里只是把图像绘制到了圆形对应的正方形边界矩形里,但并没有对图像进行圆形裁剪,所以最终输出的还是矩形图片。
问题根源
Rect.fromCircle(center: Offset(100,100), radius:100) 生成的是一个边长为200的正方形(从(0,0)到(200,200)的矩形),drawImageRect只是把源图像的指定区域拉伸到这个正方形内,Canvas默认会完整绘制整个矩形区域的内容,不会自动根据圆形形状裁剪掉多余部分。
两种可行的修正方案
方案1:使用裁剪路径(ClipPath)
通过clipPath创建圆形裁剪区域,让后续绘制的图像只在圆形范围内显示:
import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/services.dart'; import 'package:flutter/painting.dart'; Future<Uint8List> getBytesFromCanvas(int width, int height, String urlAsset) async { final ui.PictureRecorder pictureRecorder = ui.PictureRecorder(); final Canvas canvas = Canvas(pictureRecorder); final ByteData datai = await rootBundle.load(urlAsset); final ui.Image imaged = await loadImage(Uint8List.view(datai.buffer)); // 创建圆形裁剪路径 final Path circleClipPath = Path() ..addOval(Rect.fromCircle(center: const Offset(100, 100), radius: 100)); // 保存当前画布状态,后续恢复避免影响其他绘制 canvas.save(); // 应用裁剪路径,后续绘制仅在路径内可见 canvas.clipPath(circleClipPath); // 调整图像位置,让图像中心与圆形中心对齐 const double circleDiameter = 200; canvas.drawImageRect( imaged, Rect.fromLTWH(0, 0, imaged.width.toDouble(), imaged.height.toDouble()), Rect.fromLTWH(100 - circleDiameter/2, 100 - circleDiameter/2, circleDiameter, circleDiameter), const Paint(), ); // 恢复画布状态,取消裁剪 canvas.restore(); final ui.Image img = await pictureRecorder.endRecording().toImage(width, height); final ByteData? data = await img.toByteData(format: ui.ImageByteFormat.png); return data!.buffer.asUint8List(); } // 补充loadImage函数示例 Future<ui.Image> loadImage(Uint8List bytes) async { final ui.Codec codec = await ui.instantiateImageCodec(bytes); final ui.FrameInfo frameInfo = await codec.getNextFrame(); return frameInfo.image; }
方案2:使用混合模式(BlendMode)
先绘制一个白色圆形作为蒙版,再用BlendMode.srcIn让图像仅在圆形区域内显示:
import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/services.dart'; import 'package:flutter/painting.dart'; import 'package:flutter/material.dart'; Future<Uint8List> getBytesFromCanvas(int width, int height, String urlAsset) async { final ui.PictureRecorder pictureRecorder = ui.PictureRecorder(); final Canvas canvas = Canvas(pictureRecorder); final ByteData datai = await rootBundle.load(urlAsset); final ui.Image imaged = await loadImage(Uint8List.view(datai.buffer)); // 先绘制白色圆形作为蒙版 final Paint circleMaskPaint = Paint()..color = Colors.white; canvas.drawCircle(const Offset(100, 100), 100, circleMaskPaint); // 使用srcIn混合模式,仅保留图像与圆形重叠的部分 final Paint imagePaint = Paint()..blendMode = BlendMode.srcIn; const double circleDiameter = 200; canvas.drawImageRect( imaged, Rect.fromLTWH(0, 0, imaged.width.toDouble(), imaged.height.toDouble()), Rect.fromLTWH(100 - circleDiameter/2, 100 - circleDiameter/2, circleDiameter, circleDiameter), imagePaint, ); final ui.Image img = await pictureRecorder.endRecording().toImage(width, height); final ByteData? data = await img.toByteData(format: ui.ImageByteFormat.png); return data!.buffer.asUint8List(); } // 补充loadImage函数示例 Future<ui.Image> loadImage(Uint8List bytes) async { final ui.Codec codec = await ui.instantiateImageCodec(bytes); final ui.FrameInfo frameInfo = await codec.getNextFrame(); return frameInfo.image; }
关键注意点
- 两种方案都调整了图像的绘制位置,确保图像中心与圆形中心对齐,避免裁剪出的圆形图像偏移
- 方案1的
save()/restore()是好习惯,能避免裁剪影响画布上的其他绘制内容 - 方案2的
BlendMode.srcIn要求蒙版颜色不透明(这里用白色),否则会出现半透明效果
内容的提问来源于stack exchange,提问作者Karel Debedts
相关产品推荐
相关产品推荐

