# flutter图片编辑 **Repository Path**: just_single/flutter-image-editing ## Basic Information - **Project Name**: flutter图片编辑 - **Description**: flutter开源图片编辑仓库 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-06 - **Last Updated**: 2026-04-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 图片标注编辑器 这是一个基于 Flutter 的跨平台图片标注编辑器示例,当前代码主流程覆盖从相册选图、进入编辑、绘制标注、文字输入、毛玻璃马赛克、裁剪旋转,到导出并保存到系统相册的完整链路。 项目整体偏向“微信式图片标注”体验,核心目标不是截屏式拼装,而是基于统一坐标系和统一渲染逻辑进行编辑、裁剪和导出,尽量保证编辑页、裁剪页和最终保存结果保持一致。 交互参考资源位于 `dest_image/`,运行时图标资源位于 `assets/images/`。 ## 当前能力 - 从系统相册选择图片并进入编辑页 - 提供 5 种标注工具 - 自由线 - 矩形 - 箭头 - 文本 - 毛玻璃马赛克 - 支持单指命中选中对象 - 支持拖动已选中的标注对象 - 支持拖动标注手柄进行缩放或调整 - 支持删除当前选中的标注 - 支持双指缩放和平移整张画布 - 支持颜色选择和笔触粗细调整 - 支持撤销与重做 - 支持文本原位编辑 - 支持进入裁剪页后继续旋转、还原和裁剪 - 裁剪框支持拖动四个角、四条边以及整体移动 - 拖动裁剪框时显示九宫格辅助线 - 裁剪结束后自动适配到屏幕范围 - 支持导出编辑结果并保存到系统相册 ## 交互说明 ### 首页 - 点击“选择图片”打开系统相册 - 选择成功后进入编辑页 - 选择过程中按钮会进入加载态,避免重复点击 ### 编辑页 - 单指点击优先命中当前已选中的标注及其手柄,再回退命中其他标注 - 双指手势用于缩放和平移整张底图画布 - 单指拖动可以执行以下行为之一 - 绘制新的标注草稿 - 移动当前选中的标注 - 拖动当前选中标注的手柄进行缩放 - 文本工具会在画布上原位弹出 `EditableText` - 切换颜色或粗细后,新建标注会使用新样式;已选中标注也会同步应用对应样式 - 顶部栏提供“取消 / 撤销 / 恢复 / 完成(保存)” - 选中对象后,底部工具区会切换为删除按钮,并保留颜色与粗细调整入口 - 未选中对象时,底部工具区提供自由线、矩形、箭头、文本、马赛克、裁剪入口 ### 裁剪页 - 使用与编辑页一致的预览渲染思路显示底图和标注 - 裁剪框支持以下交互 - 拖动四个角调整宽高 - 拖动四条边单独调整上下左右边界 - 拖动裁剪框内部整体平移 - 拖动过程中显示九宫格辅助线,便于构图 - 松手后会在 300ms 后自动缩放到适合画布的视口 - 支持顺时针旋转 90 度 - 支持一键还原到初始裁剪状态 - 确认后返回编辑页,并重新加载裁剪后的稳定结果 ## 项目特点 ### 统一世界坐标 所有标注都记录在图片世界坐标中,而不是直接记录屏幕坐标。这样可以保证以下场景的计算更稳定: - 画布缩放和平移 - 标注命中测试 - 标注拖动与缩放 - 裁剪区域映射 - 最终离屏导出 坐标换算由 `EditorViewport` 统一负责,核心接口为: - `worldToScreen` - `screenToWorld` ### 统一渲染链路 `EditorCanvasPainter` 同时服务于以下场景: - 编辑页实时预览 - 裁剪页向量预览 - 导出结果离屏渲染 这样可以显著降低“编辑时看到的效果”和“保存后的结果”不一致的问题。 ### 非截屏式导出 项目不会直接对当前屏幕做截图保存,而是重新离屏绘制导出内容。导出流程会: 1. 计算有效导出边界 2. 将底图和标注映射到导出视口 3. 用统一 painter 重新绘制 4. 必要时收紧边界,尽量移除无效黑边或透明边 5. 输出 PNG 或 JPG,并继续走裁剪或保存逻辑 ### 马赛克统一合成 所有马赛克区域不会逐块重复叠加模糊,而是先合并区域,再统一做毛玻璃模糊绘制,避免局部反复叠加后越来越糊。 ## 页面与职责 ### `lib/pages/home_page.dart` - 首页入口 - 调用 `image_picker` 从相册选择图片 - 选图成功后跳转到 `ImageEditorPage` ### `lib/pages/editor_page.dart` 这是项目的核心控制页,负责: - 图片初始化与解码 - 画布缩放和平移 - 标注的新建、命中、选中、移动、缩放 - 文本编辑态切换与提交 - 撤销与重做 - 裁剪预热与裁剪确认 - 离屏导出 - 保存到系统相册 ### `lib/pages/editor_models.dart` 这是项目的模型层与绘制层,负责: - 定义工具、交互状态与手柄枚举 - 定义各种标注对象 - 管理世界坐标与屏幕坐标换算 - 绘制底图、马赛克、路径、文本和选中态 - 提供命中测试、几何计算和缩放逻辑 ### `lib/pages/crop_page.dart` 负责承接编辑页导出的预览上下文,提供: - 位图预览或向量预览加载 - 裁剪框绘制与遮罩绘制 - 四角、四边、整体移动三类裁剪交互 - 九宫格辅助线 - 旋转、还原、自动缩放 - 确认裁剪后将结果回传给编辑页 ## 核心数据模型 ### 工具与交互状态 - `EditorTool` - `freehand` - `rectangle` - `arrow` - `text` - `mosaic` - `CanvasOperation` - `none` - `transform` - `draw` - `moveSelected` - `resizeSelected` - `pendingTap` - `AnnotationHandle` - 自由线缩放手柄 - 矩形四角手柄 - 箭头起点和终点手柄 - 文本缩放手柄 ### 视口与历史 - `EditorViewport` - 负责图片按 `contain` 方式适配到画布 - 负责用户缩放与平移叠加 - 提供世界坐标与屏幕坐标互转 - `EditorSnapshot` - 用于撤销和重做 - 记录图片字节、标注列表、当前颜色和当前笔刷粗细 ### 标注对象 - `EditorAnnotation` - 所有标注的抽象基类 - 统一约定克隆、改色、改粗细、平移、绘制和命中测试能力 - `FreehandAnnotation` - 自由线 - `RectAnnotation` - 矩形 - `ArrowAnnotation` - 箭头,内部构建箭头 Path - `TextAnnotation` - 文本,负责测量文本尺寸、包围盒和命中区域 - `MosaicAnnotation` - 马赛克区域,本身不参与普通选中 ## 渲染与导出 ### 编辑页渲染顺序 `EditorCanvasPainter.paint` 的主顺序是: 1. 绘制背景 2. 绘制底图 3. 合并所有马赛克区域并统一做模糊绘制 4. 绘制普通标注 5. 绘制当前选中标注 6. 绘制选中手柄 7. 绘制未提交的草稿标注 ### 裁剪页渲染特点 - 裁剪页先基于当前视口绘制旋转后的内容 - 再绘制外层半透明遮罩 - 再绘制裁剪框边框、四角手柄 - 拖拽中额外绘制九宫格辅助线 ### 导出策略 编辑结果导出时不会依赖当前屏幕像素,而是根据有效内容重新计算导出区域。这样能避免: - 无效黑边被导出 - 屏幕缩放比例影响成品清晰度 - 裁剪页和保存结果不一致 项目中同时存在以下几类导出能力: - 生成可用于裁剪页首帧显示的轻量预览 - 生成裁剪确认时需要的高精度源图 - 将最终结果编码为 PNG - 将原始 RGBA 像素编码为 JPG 文件或 JPG 字节 ## 关键流程 ### 1. 选图进入编辑 `HomePage._pickImage` -> `ImagePicker.pickImage` -> `Navigator.push(ImageEditorPage)` -> `ImageEditorPage` 初始化图片与画布状态 ### 2. 标注与手势处理 `_handlePointerDown` -> 在单指按下瞬间决定选中策略 -> 优先命中当前选中对象及其手柄 -> 未命中时再检测其他标注 `_handleScaleStart / _handleScaleUpdate / _handleScaleEnd` -> 统一处理双指缩放和平移 -> 统一处理单指绘制草稿 -> 统一处理移动选中对象 -> 统一处理拖动手柄缩放选中对象 -> 统一处理点击进入文本编辑或取消选中 ### 3. 文本编辑 `_beginTextEditing` -> 建立 `_editingTextId` 与 `_editingWorldPosition` -> 显示原位 `EditableText` -> `_handleTextChanged` -> `_commitTextEditing` -> 更新已有文本或新增 `TextAnnotation` ### 4. 裁剪 `_openCrop` -> 提交当前文本编辑 -> 创建裁剪导出上下文 -> 预热裁剪预览图与高精度源图缓存 -> 打开 `CropPage` `CropPage` -> 加载延迟数据或直接应用预览 -> 命中裁剪框角、边或内部区域 -> 拖动更新裁剪框 -> 结束后延时自动适配视口 -> 确认裁剪 -> 回调编辑页执行真正的裁剪与重新加载 ### 5. 保存到系统相册 `_saveToGallery` -> 提交当前文本编辑 -> 渲染导出结果 -> 请求相册权限 -> 调用 `SaverGallery.saveImage` -> 通过 `SnackBar` 提示结果 ## 关键函数索引 ### `lib/pages/home_page.dart` - `_pickImage` - 打开相册、选择图片并进入编辑页 - `build` - 首页界面 ### `lib/pages/editor_page.dart` - `_initialize` - 读取初始图片字节或路径 - `_applyBaseImage` - 解码底图并重置编辑状态 - `_recordSnapshot` - 记录撤销快照 - `_jumpToHistory` - 跳转到指定历史节点 - `_undo` - 撤销 - `_redo` - 重做 - `_handlePointerDown` - 单指按下时的选中策略入口 - `_handleScaleStart` - 判断是画布变换、对象拖动、拖手柄还是开始绘制 - `_handleScaleUpdate` - 持续执行当前手势对应操作 - `_handleScaleEnd` - 提交草稿、结束拖拽并记录快照 - `_handleTap` - 轻触时处理文本编辑和取消选中 - `_beginTextEditing` - 开始编辑文本 - `_measureTextWorldBox` - 测量文本在世界坐标中的包围盒 - `_commitTextEditing` - 提交文本编辑结果 - `_renderVisiblePng` - 导出当前编辑结果 - `_createCropExportSession` - 计算裁剪所需的导出上下文 - `_openCrop` - 打开裁剪页 - `_saveToGallery` - 保存结果到系统相册 ### `lib/pages/editor_models.dart` - `EditorViewport.worldToScreen` - 世界坐标转屏幕坐标 - `EditorViewport.screenToWorld` - 屏幕坐标转世界坐标 - `ArrowAnnotation._buildArrowPath` - 构建箭头轮廓路径 - `TextAnnotation.textPainter` - 文本测量与排版 - `TextAnnotation.screenRect` - 文本选中区域 - `MosaicAnnotation.draw` - 单个马赛克区域绘制逻辑 - `EditorCanvasPainter.paint` - 统一渲染入口 - `EditorCanvasPainter._paintMergedMosaicLayer` - 合并马赛克区域后统一模糊 - `resizeAnnotation` - 根据手柄类型缩放标注 - `distanceToSegment` - 路径命中测试基础几何函数 ### `lib/pages/crop_page.dart` - `_loadDeferredData` - 延迟装载裁剪页所需数据 - `_loadSourceImage` - 解码裁剪预览位图 - `_resolveHandle` - 判断命中角、边还是内部移动区 - `_handlePanStart` - 开始裁剪拖拽 - `_handlePanUpdate` - 更新裁剪框位置与大小 - `_fitViewRectToCanvas` - 计算适配屏幕的新视口 - `_scheduleZoomToCrop` - 300ms 后自动缩放到裁剪区域 - `_rotate` - 裁剪页旋转 90 度 - `_reset` - 裁剪页还原 - `_confirmCrop` - 生成裁剪结果并回传编辑页 ## 推荐阅读顺序 如果要快速理解项目代码,建议按下面顺序阅读: 1. `lib/pages/editor_models.dart` 2. `lib/pages/editor_page.dart` 3. `lib/pages/crop_page.dart` 4. `lib/pages/home_page.dart` ## 运行方式 ### 环境要求 - Flutter SDK 3.7 或更高 - Dart SDK 3.7 或更高 ### 安装依赖 ```bash flutter pub get ``` ### 运行项目 ```bash flutter run ``` ## 依赖说明 当前 `pubspec.yaml` 中的关键依赖如下: - `image` - 位图裁剪、旋转、编码 - `image_picker` - 从系统相册选择图片 - `path` - 路径拼接 - `path_provider` - 获取临时目录,缓存编辑结果 - `permission_handler` - 请求相册相关权限 - `saver_gallery` - 保存图片到系统相册 ## 目录结构 ```text lib/ main.dart pages/ home_page.dart editor_page.dart editor_models.dart crop_page.dart assets/ images/ dest_image/ ``` ## 当前已实现但需要知晓的行为约束 - 裁剪页的自动适配会在拖拽结束后延迟 300ms 触发 - 马赛克采用毛玻璃模糊效果,不是像素块效果 - 文本编辑使用原位输入框,提交前属于临时编辑态 - 保存到系统相册依赖平台权限状态 - 编辑历史基于快照记录,不是增量 patch ## 适用平台 - Android - iOS 当前代码结构也可继续扩展到其他 Flutter 支持的平台,但现有交互和保存链路主要围绕移动端图片编辑场景实现。