这是第 93 篇不掺水的原创,想获取更多原创好文,请搜寻公众号关注咱们吧~ 本文首发于政采云前端博客:Flutter UI调试小工具——色彩吸管

前言

作为客户端开发, 在利用交付之前, 个别都会有 UI 走查这一环节. 一方是对色彩不敏感的开发另一方是对色彩非常敏感的视觉是否经常出现下列对话:

视觉: 你这个色彩是不是和我设计的不太一样.

开发: 哪里不一样, 这个跟设计稿的色彩截然不同.

视觉: 设计稿明明是伸手不见五指的黑, 你这个黑的不够纯正.

开发: 你别走, 等我看下代码.

......

看代码, 不失为一个方法. 然而如果你在其余的分支, 你须要先 stash 本地代码, 再切分支, 看代码, 找色彩... 这个时候, 是不是特地想有一个工具, 能够立马查看理论显示的色彩,

上面来介绍我是如何制作一个色彩吸管工具, 来当场"打脸", 当然个别都是"被打脸"。

  
 

把大象装到冰箱, 须要三步: 1. 关上冰箱. 2. 把大象装进去, 3. 关上冰箱. 那制作一个色彩吸管须要几步呢?

  1. 获取以后屏幕色彩
  2. 选取指定地位
  3. 色彩输入

1. 获取所有像素点的色彩

如何获取以后屏幕的所有像素点的色彩呢, 挨个组件去取不太事实. 咱们能够曲线救国, 对以后屏幕截屏, 截到的内容就是正在显示的色彩. 那么如何截屏呢, Flutter 提供了一个 Widget RepaintBoundary. 只需将内容用 RepaintBoundary 包裹起来:

Widget build(BuildContext context) {  return RepaintBoundary(    key: _key,    child: Scaffold(      appBar: AppBar(        title: Text(widget.title),      ),      body: Container(),    ),  );}

在须要截屏的中央, 通过 _key 获取到指定 RenderRepaintBoundary , 就能够间接转化为图片, 代码如下:

// 依据key获取须要截图的组件RenderRepaintBoundary boundary = _key.currentContext.findRenderObject();// 获取以后设施像素比double pix = window.devicePixelRatio;// 截屏var image = await boundary.toImage(pixelRatio: pix);

至此, 咱们就失去了以后屏幕的截图. 图片能够看成是一组依照非凡的数据结构, 以 png 图片来讲, 一个 png 图片是由文件署名和数据块 (chunk) 两局部组成. 数据块又由要害数据块 (critical chunk) 和辅助数据块 (ancillary chunk) 两局部组成. 这些数据块蕴含了该图片的所有信息, 例如: 图像的宽高, 色彩类型, 图像深度, 理论图像数据, 图像地位信息, 最初批改信息等.更多内容能够参考这里。

图像数据块 (IDAT) 属于要害数据块, 其中保留了图片的理论图像数据, 联合色彩类型(常见的有 RGB, YUV 等)也就能够获取到所有像素的指定色彩. 至此, 第一步完结。

2. 获取指定像素点的色彩

咱们如何取得指定像素点的色彩呢, 当然是用手选了, 想看哪里点哪里, 最为不便. 这个实现起来也很简略. 将后面截屏失去的图片通过 Image.memory() 办法展现进去, 不过须要做个数据转换, 代码如下:

// 将Image类型转换为Uint8List类型ByteData byteData = await image.toByteData(format: ImageByteFormat.png);Uint8List pngBytes = byteData.buffer.asUint8List();

将下面的图片加上一个 GestureDetector widget, 在 onPanUpdate 或者 onTapUp 办法中能够轻易的获取到以后的 offset . 那么有了图片所有像素的色彩值, 有了图片的偏移量, 如何获取指定偏移量地位的色彩值呢, 这里就须要用到一个驰名的图片解决库 image。他提供了getPixelSafe()办法, 传入 x, y 值就能够取得以后地位的色彩值类型( Uint32 的 AABBGGRR 格局)。 代码如下:

Color getColorFromDragUpdateDetails(Offset globalPosition) {  int x = globalPosition.dx.toInt();  int y = globalPosition.dy.toInt();  double pix = window.devicePixelRatio; //获取以后设施像素比  int pixel32 = this.temp.getPixelSafe((x * pix).toInt(), (y * pix).toInt());  int argb = _abgrToArgb(pixel32);  Color pixelColor = Color(argb);  print('以后坐标: x:$x, y:$y');  print('--------ARGB:$argb');  print('--------HEX:${argb.toRadixString(16).toUpperCase()}');  print('--------A:${pixelColor.alpha} R:${pixelColor.red} G:${pixelColor.green}B:${pixelColor.blue}');  return pixelColor;}

image 库的大抵原理如下, 将不同后缀的图片依照固定的解析形式, 获得其中的数据, 图片的像素被编码为 4 字节的 Uint32 整数, 依据传入的 x, y 值, 去取对应地位的色彩值就能够了。
咱们再加一个悬浮窗来显示选中的色彩, 最终的展现成果如下:

你认为到这里就完了吗, NO~ NO~ NO~尽管满足了咱们最后的性能, 然而还很难用, 在"细微"的手指遮挡下, 咱们根本无法做到像素级抉择和挪动。要是能对选中的中央做个放大就完满了。

3. 放大选中地位

在 Flutter 中, 对图片的操作能够通过 ImageFilter 来实现.ImageFilter 提供了两个构造方法:

// 提供一个能够实现高斯含糊的图片滤镜ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 })// 通过利用一个矩阵的变换对图片做操作ImageFilter.matrix(Float64List matrix4, { FilterQuality filterQuality = FilterQuality.low })

咱们在这里能够应用 ImageFilter.matrix() 来对图片的的纹理做矩阵变换来实现图片的放大成果. 放大成果分两步走:

3.1 取得放大指定地位后的图片矩阵

这个很好了解, 咱们将上一阶段截屏失去的图片用 GestureDetector 包裹, 在 onPanUpdate 时, 取到对应地位的坐标, 而后对截图进行矩阵变换, 取得变换过后的纹理:

// 手指挪动时onPanUpdate: (detail) {  setState(() {    // 获取以后选中点的色彩值    Color pixelColor =      getColorFromDragUpdateDetails(detail.globalPosition);    choiceColor = pixelColor;    choiceColorString = "0x${pixelColor.value.toRadixString(16).toUpperCase()}";    // 以后选中的点    _magnifierPosition =      detail.globalPosition - _size.center(Offset.zero);    double newX = detail.globalPosition.dx;    double newY = detail.globalPosition.dy;    // 矩阵变换    final Matrix4 newMatrix = Matrix4.identity()      ..translate(newX, newY)      ..scale(scale, scale)      ..translate(-newX, -newY);    // 保留变换过后的矩阵    matrix = newMatrix;  });}

3.2 创立一个追随组件 & 利用矩阵

这个是惯例操作啦, 应用 StackPositioned 就能够实现一个追随手势的组件, 而后创立一个 BackdropFilter 组件, 将下面变换过得矩阵利用到 ImageFilter 上.。在地位变动时, 实时 setState, 触发组件的刷新, 就能够做到啦。特别强调的是, 因为获取到的矩阵是整张图片变换的残缺矩阵, 这里须要应用 ClipRRect 组件, 将不须要显示的局部裁减掉。

Visibility(  visible: _visible,  child: Positioned(    left: _magnifierPosition.dx,    top: _magnifierPosition.dy,    child: ClipRRect(      borderRadius: BorderRadius.circular(_size.longestSide),      child: BackdropFilter(        filter: ImageFilter.matrix(matrix.storage),        child: CustomPaint(          painter: painter,          size: _size,        ),      ),    ), ))

最终成果如下所示:

4.遇到的问题

到这里, 这篇文章就根本完结了, 这里记录一下遇到的一些问题:

4.1 色彩编码

在获取图片色彩时, 获取到的理论是 AABBGGRR 色彩类型, 而 Flutter 个别应用的是 AARRGGBB 色彩类型, 这里还须要做一个转换, 具体代码如下:

// AABBGGRR -> AARRGGBBint _abgrToArgb(int oldValue) {  int newValue = oldValue;  newValue = newValue & 0xFF00FF00; //open new space to insert the bits  newValue = ((oldValue & 0xFF) << 16) | newValue; // change BB  newValue = ((oldValue & 0x00FF0000) >> 16) | newValue; // change RR  return newValue;}// int类型的值转换为16进制的hex值String hexColor = argb.toRadixString(16).toUpperCase();

理论更为常见的还有 YUV 类型。 YUV 又有好多子类型, 例如 YUV420, YUV421 等, 读者能够自行理解相干材料。此处再扩大一个问题, 如何计算一张图片的理论内存大小? 图片的内存大小是和分辨率和色彩类型无关的, 分辨率决定了有多少个像素点, 色彩类型决定了一个像素点存储了多大的数据, 一般来讲, 图片内存大小的计算公式 宽度*高度 *bytesPerPixel / 8。例如一张 1000*1000 分辨率, RGB 色彩类型的图片,通常状况下, 图片主动缩放到 2 的 n 次方大小, RGB 色彩空间下每个色彩重量由 8 位组成, 然而通常状况下色彩还有 alpha 通道也是 8 位 也就是传说中的 RGBA , 所以总共是 32 位。所以个别图片的计算公式是 w*h*4。该张图片理论占用的大小就是 1024*1024 * 4 / 1024 / 1024 = 4MB。过后理论状况可能会比这个更为简单, RGBA 类型也还有许多更加节俭内存的变种, 例如 RGBA8888, RGBA4444 等。图片蕴含的其余 chunk 也会占用肯定的内存大小, 此处只是做一个补充, 读者可自行学习。

4.2 获取指定地位的色彩

在截图时, 咱们传入了 double pix = window.devicePixelRatio; 设施像素比。 以 iPhone11 为例, pix 的值为 2.0。在前面咱们获取到设施的触摸点时, 触摸点的地位是以物理尺寸为准, 所以去取图片也要将该 pix 值利用进去。

4.3 矩阵变换

此例中, 咱们要做的事, 放大图片的指定地位。通过矩阵来示意的话, 就是矩阵的平移和缩放的组合. 咱们须要先将矩阵平移到须要缩放的点, 缩放, 缩放实现后再平移回去。因为缩放默认是以原点坐标为基准,原点坐标默认是左上角的 (0, 0) 地位。所以咱们须要缩放的点平移到原点, 再缩放, 缩放完之后复原现场. 矩阵变动很有意思, 此处不再做扩大, 读者能够自行开掘更多玩法.

5.写在最初

纵观全局, 没有用到什么特地难或者浅近的技术, 然而组合进去的这个小工具却很有实用价值. 当然在UI还原度的晋升和UI开发效率方面还有很多其余能够做的事件, 例如: 检测组件大小, 组件的地位, 组件层级等多种形式.

在晋升 UI 还原度的和开发效率方面, 业界一些大厂在这方面曾经走得挺远了, 例如爱奇艺. 他们曾经做到了UI半自动验收. 大抵实现思路是利用 AI 来辨认组件边界, 而后通过控件匹配算法和间距抉择算法来建设开发页面与设计页面的控件之间的一对一关系和间距关系. 而后将这些关系一一比对, 就可能输入匹配的后果. 然而这种形式在精密度和准确度下面必定不如应用各种工具进行测量, 然而胜在效率高.

我感觉将来的 UI 自动化验收肯定是 AI 辨认为主的主动验收模式和人工测量为主的个性化验收模式相结合. 在页面构造清晰, 组件不多的页面以主动验收为主, 在页面结构复杂的页面以人工验收为主. 这样能力做到效率和准确度的最好联合.

最初,用我不晓得从哪里看到的一句话来完结吧, 共勉~

技术是为了解决业务问题的,只有在实现业务、给人们带来便当的前提下,技术的存在才有意义。

举荐浏览

如何用 JS 实现二叉堆

编写高质量可保护的代码:程序范式

招贤纳士

政采云前端团队(ZooTeam),一个年老富裕激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员形成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端利用、数据分析及可视化等方向进行技术摸索和实战,推动并落地了一系列的外部技术产品,继续摸索前端技术体系的新边界。

如果你想扭转始终被事折腾,心愿开始能折腾事;如果你想扭转始终被告诫须要多些想法,却无从破局;如果你想扭转你有能力去做成那个后果,却不须要你;如果你想扭转你想做成的事须要一个团队去撑持,但没你带人的地位;如果你想扭转既定的节奏,将会是“5 年工作工夫 3 年工作教训”;如果你想扭转原本悟性不错,但总是有那一层窗户纸的含糊… 如果你置信置信的力量,置信平凡人能成就不凡事,置信能遇到更好的本人。如果你心愿参加到随着业务腾飞的过程,亲手推动一个有着深刻的业务了解、欠缺的技术体系、技术发明价值、影响力外溢的前端团队的成长历程,我感觉咱们该聊聊。任何工夫,等着你写点什么,发给 ZooTeam@cai-inc.com