Flutter 像素编辑器#03 | 像素图层


theme: cyanosis

本系列,将通过 Flutter 实现一个全平台的像素编辑器应用。源码见开源项目 【pix_editor】

  • 《Flutter 像素编辑器#01 | 像素网格》
  • 《Flutter 像素编辑器#02 | 配置编辑》
  • 《Flutter 像素编辑器#03 | 像素图层》
  • 上一篇我们实现了编辑配置,可以设置网格数、背景色、画笔颜色。本篇将引入 图层 的概念,支持新建图层进行绘制,各图层间的像素内容互不干涉,可以点击切换激活的图层进行编辑,效果如下:

110.gif


1. 需求分析与数据规划

在当前功能中,展示数据由单个变为了列表。此时希望每个图层都可以独立配置网格数量,可以将行列数视为视图中的数据,每层独立维护。另外,有一个很重要的优化点:

如下所示,需要在图层中展示缩小版的当前内容。如果重新画一遍,那么每次视图变化就会绘制 两次相同内容,包括遍历像素点数据,这是颜色、绘制矩形。有种方式可以优化这种绘制场景,那就是 canvas.drawPicture

image.png


将两处的绘制使用同一个 Picture 图形,通过缩放的方式实现大小的不同。如下所示,定义 PaintLayer 作为图层的顶层抽象,其中持有 Picture 数据,通过 update 方法创建或更新图形数据。这里绘制视口统一使用 1024*1024 ; 并抽象出 paint 方法,处理绘制逻辑:

```dart abstract class PaintLayer { static Size kPaintViewPort = const Size(1024, 1024); String name; final String id;

late Picture picture;

PaintLayer({ required this.id, this.name = '新建图层', });

void update() { PictureRecorder recorder = PictureRecorder(); Canvas canvas = Canvas(recorder); paint(canvas, kPaintViewPort); picture = recorder.endRecording(); }

void paint(Canvas canvas, Size size); } ```


然后派生出 PixLayer 负责绘制像素图层,其中持有行列格数和像素数据列表 pixCells。然后实现 paint 方法,在 1024*1024 的画板上绘制内容:

```dart class PixLayer extends PaintLayer { int row; int column; final List pixCells;

PixLayer({ required this.row, required this.column, required this.pixCells, super.name, required super.id, });

@override void paint(Canvas canvas, Size size) { Paint cellPaint = Paint(); double side = min(size.width, size.height); double stepH = side / column; double stepW = side / row; for (int i = 0; i < pixCells.length; i++) { PixCell cell = pixCells[i]; double top = cell.position.$1 * stepW; double left = cell.position.$2 * stepH; Rect rect = Rect.fromLTWH(top, left, stepW, stepH); canvas.drawRect(rect.deflate(-0.2), cellPaint..color = cell.color); } } } ```


2.业务逻辑处理

此时处理绘制逻辑的 PixPaintLogic 类,需要维护 PaintLayer 列表数据,由于需要切换激活的图层,使用维护 activeLayerId 作为激活索引。另外,基于激活图层和图层列表,可以提供一些 get 方法便于访问数据:

```dart class PixPaintLogic with ChangeNotifier {

String activeLayerId = ''; final List _layers = [];

PixLayer get activePixLayer => _layers.whereType ().singleWhere((e) => e.id == activeLayerId);

List get pixCells => activePixLayer.pixCells;

int get row => activePixLayer.row;

int get column => activePixLayer.column; ```


  • 添加图层: addPixLayer 处理逻辑

使用 Uuid 作为唯一标识,创建 PixLayer 对象,并加入 _layers 列表中,如果有激活的索引,插入在它上方:并触发 changeActiveLayer 方法,更新激活索引

111.gif

```dart PixPaintLogic() { addPixLayer(); }

void addPixLayer() { int activeIndex = 0; if (activeLayerId.isNotEmpty) { activeIndex = _layers.indexWhere((e) => e.id == activeLayerId); } String id = const Uuid().v4(); PixLayer pixLayer = PixLayer(name: "像素图层", row: 32, column: 32, pixCells: [], id: id) ..update(); _layers.insert(activeIndex, pixLayer); changeActiveLayer(id); } ```


  • 激活图层: changeActiveLayer 处理逻辑

激活图层非常简单,需要更新 activeLayerId ,并通过 activePixLayer.update 更新图层中的 picture 数据即可。这样在 notifyListeners 之后,两处的绘制逻辑中访问的就是新版的 picture 对象。

dart void changeActiveLayer(String layerId) { activeLayerId = layerId; activePixLayer.update(); notifyListeners(); }


  • 删除激活图层:removeActiveLayer 处理逻辑

删除图层时,当只有一个时禁止删除。如果当前激活图层不是最后一个,删除后会激活下一个图层。如果激活图层是最后一个,则激活前一个:

112.gif

```dart void removeActiveLayer() { if (_layers.length == 1) { return; }

int currentIndex = 0; int activeIndex = 0;

if (activeLayerId.isNotEmpty) { currentIndex = _layers.indexWhere((e) => e.id == activeLayerId); } if (currentIndex == _layers.length - 1) { activeIndex = currentIndex - 1; } else { activeIndex = currentIndex + 1; } activeLayerId = _layers[activeIndex].id; _layers.removeAt(currentIndex); notifyListeners(); } ```


3. 视图层处理

首先中间区域的 PixEditorPainter 绘制逻辑中,通过缩放的方式,对激活图层中的 picture 进行绘制:

image.png

```dart @override void paint(Canvas canvas, Size size) { Paint bgPaint = Paint()..color = config.backgroundColor; canvas.drawRect(Offset.zero & size, bgPaint);

/// 绘制激活图层的 picture canvas.save(); double rate = size.height / PaintLayer.kPaintViewPort.height; canvas.scale(rate); canvas.drawPicture(pixPaintLogic.activePixLayer.picture);

canvas.restore(); if (config.showGrid) { drawGrid(canvas, size); } } ```


缩略图封装为 LayerPreview 组件,使用 LayerPreviewPainter 进行绘制。其中绘制的内容也是激活图层中的 picture 对象,以此实现了两个绘制区域,使用同一份绘制资源:

image.png

```dart class LayerPreview extends StatelessWidget { final Picture picture;

const LayerPreview({super.key, required this.picture});

@override Widget build(BuildContext context) { return CustomPaint( painter: LayerPreviewPainter(picture), ); } }

class LayerPreviewPainter extends CustomPainter { final Picture picture;

LayerPreviewPainter(this.picture);

@override void paint(Canvas canvas, Size size) { canvas.drawRect(Offset.zero & size, Paint()..color = Colors.white); canvas.save(); canvas.translate((size.width - size.height) / 2, 0); double rate = size.height / PaintLayer.kPaintViewPort.height; canvas.scale(rate); canvas.drawPicture(picture); canvas.restore();

canvas.drawRect(Offset.zero & size, Paint()..style = PaintingStyle.stroke);

}

@override bool shouldRepaint(covariant LayerPreviewPainter oldDelegate) { return picture != oldDelegate.picture; } } ```


图层的操作面板,使用 ListView 展示 PaintLayer 列表数据。条目构造的细节没什么难度,就不多说了,可以详见源码。

image.png

dart ListView.separated( separatorBuilder: (_, __) => const Divider(), itemBuilder: (_, index) => LayerItem( onSelectLayer: (layer) { logic.changeActiveLayer(layer.id); }, active: layers[index].id == logic.activeLayerId, layer: layers[index], ), itemCount: layers.length, ),


目前为止,已经引入了图层的概念,并且支持新建、删除、切换图层。至于图层更多的功能、比如锁定、合并、复制、编辑等,将在后续逐渐完善。那本文就到这里,谢谢观看~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/553103.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【R语言】组合图:散点图+箱线图+平滑曲线图+柱状图

用算数运算符轻松组合不同的ggplot图&#xff0c;如图&#xff1a; 具体代码如下&#xff1a; install.packages("devtools")#安装devtools包 devtools::install_github("thomasp85/patchwork")#安装patchwork包 library(ggplot2) library(patchwork) #p1是…

Spark/SparkSQL读取Hadoop LZO文件概述

一、前置配置 IDEA Maven安装配置 Scala&#xff08;可选&#xff09; Java Hadoop.dll&#xff08;可能需要&#xff0c;具体看有无相关错误信息&#xff09; hadoop-lzo-0.xx.xx.jar&#xff08;如果你的版本过高&#xff0c;需要到官网下载高版本&#xff0c;mvnrepository仓…

2024年——区块链技术进入全新高度

BTC生态蓬勃发展&#xff0c;以太坊的L1和L2模块化重塑智能合约生态。RAAS&#xff08;区块链即服务&#xff09;、Depin、并行EVM等技术的崛起&#xff0c;为区块链应用提供了更高的性能和可扩展性。以太坊再质押成为焦点。技术创新与日俱进&#xff0c;一同探索这个充满活力的…

优思学院|ISO45001职业健康安全管理体系是什么?

ISO45001:2018是新公布的国际标准规范&#xff0c;全球备受期待的职业健康与安全国际标准&#xff08;OH&S&#xff09;于2018年公布&#xff0c;并将在全球范围内改变工作场所实践。ISO45001将取代OHSAS18001&#xff0c;成为全球工作场所健康与安全的参考。 ISO45001:201…

HarmonyOS开发实例:【分布式新闻客户端】

介绍 本篇Codelab基于栅格布局、设备管理和多端协同&#xff0c;实现一次开发&#xff0c;多端部署的分布式新闻客户端页面。主要包含以下功能&#xff1a; 展示新闻列表以及左右滑动切换新闻Tab。点击新闻展示新闻详情页。点击新闻详情页底部的分享按钮&#xff0c;发现周边…

可见光相机曝光方式

可见光摄影中的曝光方式主要包括两种&#xff1a;卷帘曝光和全局曝光。它们之间的区别在于曝光过程中传感器或胶片感光部分的工作方式不同&#xff0c;这直接影响到图像捕获的效果和特性。 卷帘曝光&#xff08;Rolling Shutter&#xff09;&#xff1a; 工作原理&#xff1a;在…

工业自动化,3D视觉技术3C薄片自动化上料

随着制造业的快速发展&#xff0c;3C行业对薄片类零件的上料需求日益增长。传统的上料方式往往依赖于人工操作&#xff0c;效率低下且存在误差。为了解决这一问题&#xff0c;3D视觉技术应运而生&#xff0c;为3C薄片自动化上料提供了强大的技术支持。本文将探讨3D视觉技术如何…

美格智能出席紫光展锐第三届泛金融支付生态论坛,引领智慧金融变革向新

4月16日&#xff0c;以“融智创新&#xff0c;共塑支付产业新生态”为主题的紫光展锐第三届泛金融支付生态论坛在福州举办&#xff0c;来自金融服务机构、分析师机构、终端厂商、模组厂商等行业各领域生态伙伴汇聚一堂&#xff0c;探讨金融支付产业的机遇与挑战。作为紫光展锐重…

(4.6–4.12)投融资周报|共29笔公开投融资事件,基础设施继续领跑,游戏、RWA、Depi、NFT相关融資活躍

本周千万美金以上融资有6笔&#xff1a; 高性能的第 1 层区块链Monad完成了一轮2.25 亿美元的融资&#xff0c;投资方为Paradigm、Coinbase Ventures等。 互联网基础设施解决方案Auradine完成了8000 万美元的B轮融资&#xff0c;投资方为Celesta Capital、Mayfield等。 比特币…

利用大语言模型,矢量数据库实现数据库的智能搜索

目的 数据库使用SQL 语言查询数据&#xff0c;数据库的记录中要有一个关键字段&#xff08;通常称为主键字段&#xff0c;它的值在数据库列表中是唯一的&#xff09;,数据记录是结构化的. 如果你需要根据数据记录的内容来查询数据记录&#xff0c;就需要通过Select 语句在数据库…

【数学】主成分分析(PCA)的应用案例解析(Python)

接着上文PCA的数学详细推导过程&#xff0c;本文介绍使用Python结合图像压缩案例解释PCA的具体实现流程&#xff0c;以了解数据处理的一些方法 Jupyter Notebook file 文章目录 借助 scikit-learn 实现 PCA输入数据PCA降维并重建 手动实现 PCA 过程输入数据数据居中处理协方差矩…

自动驾驶(八十四)---------中间件对比分析

很久没有写博客了&#xff0c;CSDN无故非法删了我第82篇&#xff0c;让我很恼火&#xff0c;一直提不起兴趣重新写一遍第82篇。但回初心&#xff0c;知识需要用自己的语言输出&#xff0c;所以今天对比分析自动驾驶中间件&#xff1a; 1. 中间件介绍 在自动驾驶架构中&#xf…

【Git】git命令大全(持续更新)

本文架构 0.描述git简介术语 1.常用命令2. 信息管理新建git库命令更改存在库设置获取当前库信息 3.工作空间相关将工作空间文件添加到缓存区&#xff08;增&#xff09;从工作空间中移除文件&#xff08;删&#xff09;撤销提交 4.远程仓库相关同步远程仓库分支 &#xff08;持…

【学习笔记】Python大数据处理与分析——数据预处理

一、数据清洗 1、唯一值与重复值 获取唯一值的方法是采用unique()函数&#xff0c;用于Series对象&#xff1a; s1 pd.Series([2, 3, 4, 1, 2, 5, 3, 6, 4, 9, 5, 3, 4, 2, 1, 2])print(s1.unique()) →[2 3 4 1 5 6 9] 但unique()函数不能用于DataFrame对象&#xff0c;而d…

html接入百度地图

1.申请key key申请地址&#xff1a;https://lbsyun.baidu.com/apiconsole/key#/home 注意&#xff1a;白名单设置*则所有可访问&#xff0c;正式发布保证安全需修改为域名 官方文档 https://lbsyun.baidu.com/index.php?titlejspopularGL 2.html接入示例 <!DOCTYPE …

基于51单片机智能鱼缸仿真LCD1602显示( proteus仿真+程序+设计报告+讲解视频)

基于51单片机智能鱼缸仿真LCD显示 1. 主要功能&#xff1a;2. 讲解视频&#xff1a;3. 仿真4. 程序代码5. 设计报告6. 设计资料内容清单&&下载链接资料下载链接&#xff1a; 基于51单片机智能鱼缸仿真LCD显示( proteus仿真程序设计报告讲解视频&#xff09; 仿真图prot…

基于springboot+vue+Mysql的汽车租赁系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

04-15 周一 GitHub仓库CI服务器actions-runner和workflow yaml配置文档解析

04-15 周一 GitHub仓库CI服务器配置过程文档 时间版本修改人描述2024年4月15日10:35:52V0.1宋全恒新建文档2024年4月17日10:33:20v1.0宋全恒完成github actions CI的配置和工作流配置文件解读文档的撰写 简介 一些基础概念 前提知识 仓库介绍 地址镜像介绍https://github.…

选择生产制造项目管理系统?全面解析功能与实际应用!

生产效率和项目规划是制造企业亟需解决的难题&#xff0c;想要从容的应对这些挑战&#xff0c;离不开好用的生产制造项目管理系统。下面我们全面解析什么才能称得上是好用的生产制造项目管理系统。 一、好用的生产制造项目管理系统 什么样的项目管理系统才能算是好用呢&#x…

【QT进阶】Qt Web混合编程之VS2019 CEF的编译与使用(图文并茂超详细介绍)

往期回顾 【QT入门】Qt自定义控件与样式设计之自定义QLineEdit实现搜索编辑框-CSDN博客 【QT入门】Qt自定义控件与样式设计之自定义QTabWidget实现tab在左&#xff0c;文本水平的效果-CSDN博客【QT进阶】Qt Web混合编程之CEF、QCefView简单介绍-CSDN博客 【QT进阶】Qt Web混合编…
最新文章