You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter缩放后重绘CustomPaint线条解决绘图放大像素化问题

问题背景

我正在使用Flutter开发一款笔记应用,绘图模块基于CustomPainter实现。为优化性能,我会在绘制若干线条后将CustomPaint内容转为图片存储,但缩放放大时,已转为图片的线条会出现严重像素化问题。我最初的解决方案是缩放后重绘线条,但如何正确重绘才能保证分辨率达标?此外是否可以仅重绘当前屏幕可视区域内的线条?


现有代码参考

布局约束与像素比获取

LayoutBuilder(builder: (context, constraints) {
      size = constraints.biggest;
      scale = MediaQuery.of(context).devicePixelRatio;

缩放交互实现

GestureDetector(
  onScaleStart: (details) {
    _initialFocalPoint = details.focalPoint;
    _initialScale = _scale;
  },
  onScaleUpdate: (details) {
    setState(() {
      _sessionOffset = details.focalPoint - _initialFocalPoint;
      _scale = _initialScale * details.scale;
    });
  },
  onScaleEnd: (details) {
    setState(() {
      _offset += _sessionOffset;
      _sessionOffset = Offset.zero;
    });
  },
  child: Transform.translate(
      offset: _offset + _sessionOffset,
      child: Transform.scale(
        scale: _scale,
        child: .... 

CustomPaint 组件代码

CustomPaint(
  size: size!,
  willChange: true,
  painter: DrawingViewPainter(
      pointsList: drawingNotifier.points,
      image: image
  ),
);

绘制内容转缓存图片逻辑

ui.PictureRecorder recorder = ui.PictureRecorder();
Canvas canvas = ui.Canvas(recorder);
canvas.scale(scale!);

DrawingViewPainter(image: image, pointsList: points).paint(canvas, size!);
ui.Picture picture = recorder.endRecording();
ui.Image newImage = await picture.toImage(
  (size!.width * scale!).ceil(),
  (size!.height * scale!).ceil(),
);
// ...
image = newImage

缓存图片绘制逻辑

canvas.drawImageRect(
  image!,
  Offset.zero & Size(image!.width.toDouble(), image!.height.toDouble()),
  Offset.zero & size,
  Paint()
);

解决方案

1. 解决缩放像素化问题

当前你生成缓存图片时仅考虑了设备基础像素比,没有计入手势缩放的倍率,放大时低分辨率的缓存图被拉伸就会出现像素化。修改缓存图片生成逻辑,把手势缩放倍率也计入分辨率计算:

// 计算总缩放倍率 = 设备像素比 * 手势缩放倍率
final totalScale = scale! * _scale;
ui.PictureRecorder recorder = ui.PictureRecorder();
Canvas canvas = ui.Canvas(recorder);
canvas.scale(totalScale);

DrawingViewPainter(image: image, pointsList: points).paint(canvas, size!);
ui.Picture picture = recorder.endRecording();
ui.Image newImage = await picture.toImage(
  (size!.width * totalScale).ceil(),
  (size!.height * totalScale).ceil(),
);

优化建议:手势缩放过程中临时直接绘制原始线条,不要用缓存图,缩放结束稳定后再重新生成对应倍率的缓存图,既保证操作流畅度,也不会出现缩放过程中的模糊问题。

2. 可视区域按需重绘实现

可以通过当前偏移量_offset和缩放倍率_scale计算出屏幕可视区域对应的画布坐标范围,遍历所有线条数据时,只把坐标落在这个范围内的线条传入DrawingViewPainter绘制,能大幅减少不必要的绘制开销。

3. 更优的性能方案

优先缓存ui.Picture矢量绘制指令而不是位图,矢量数据放大不会出现像素化,需要导出图片的时候再按需生成对应分辨率的位图即可,内存占用比反复生成高分辨率位图低很多。


内容的提问来源于stack exchange,提问作者Yusuf-Uluc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 11:57:04