当前位置: 首页 > news >正文

Flutter实现PS钢笔工具,实现高精度抠图的效果。

演示:

 代码:

import 'dart:ui';import 'package:flutter/material.dart' hide Image;
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:kq_flutter_widgets/widgets/animate/stack.dart';
import 'package:kq_flutter_widgets/widgets/button/kq_small_button.dart';
import 'package:kq_flutter_widgets/widgets/update/update_view.dart';///抠图软件原型
class DrawPathTest extends StatefulWidget {const DrawPathTest({super.key});@overrideState<StatefulWidget> createState() => DrawPathTestState();
}class DrawPathTestState extends State<DrawPathTest> {///是否绑定左右操作点,即操作一个点,另一个点自动计算static bool isBind = true;///击中范围半径static double hitRadius = 5;///绘制区域state持有UpdateViewState? state;///背景图Image? _image;///历史步骤存储KqStack stackHistory = KqStack();///回收站步骤存储KqStack stackRecycleBin = KqStack();///绘制步骤集合List<Step> drawList = [];///手指按下时点击的控制点的位置缓存Step? hitControlStep;///手指按下时点击的画线点的位置缓存Step? hitDrawStep;///闭合绘制完成状态,不再添加点bool drawFinish = false;@overridevoid initState() {super.initState();_load("https://c-ssl.duitang.com/uploads/item/201903/19/20190319001325_bjvzi.jpg").then((value) {_image = value;update();});}@overrideWidget build(BuildContext context) {return Column(children: [Expanded(child: LayoutBuilder(builder: (c, lc) {return Container(color: Colors.white60,child: Listener(onPointerDown: (v) {Offset src = v.localPosition;///判断是否hithitDrawStep = _isHitDrawPoint(src);if (!drawFinish) {if (hitDrawStep != null && hitDrawStep!.isFirst) {_add(src, isLast: true);drawFinish = true;} else {hitControlStep = _isHitControlPoint(src);hitControlStep ??= _add(src);}update();} else {hitControlStep = _isHitControlPoint(src);}},onPointerMove: (v) {if (hitDrawStep != null) {_update(hitDrawStep!, v.localPosition);update();} else if (hitControlStep != null) {_update(hitControlStep!, v.localPosition);update();}},child: UpdateView(build: (UpdateViewState state) {this.state = state;return CustomPaint(size: Size(lc.maxWidth, lc.maxHeight),painter: TestDraw(_image, drawList),);},),),);}),),Row(children: [SizedBox(width: 20.r),Expanded(child: KqSmallButton(title: "撤销",onTap: (disabled) {_undo();update();},),),SizedBox(width: 20.r),Expanded(child: KqSmallButton(title: "重做",onTap: (disabled) {_redo();update();},),),SizedBox(width: 20.r),Expanded(child: KqSmallButton(title: "选择",onTap: (disabled) {_select();update();},),),SizedBox(width: 20.r),Expanded(child: KqSmallButton(title: "反选",onTap: (disabled) {_invert();update();},),),SizedBox(width: 20.r),Expanded(child: KqSmallButton(title: "删除",onTap: (disabled) {_delete();update();},),),SizedBox(width: 20.r),],),SizedBox(height: 20.r),],);}///更新绘制区域update() {state?.update();}///添加点Step _add(Offset offset, {bool isLast = false}) {Step step = Step(offset, offset, offset);step.isLast = isLast;if (drawList.isEmpty) {step.isFirst = true;}//添加到历史stackHistory.push(step);//添加到绘制列表drawList.add(step);//清除垃圾箱stackRecycleBin.clear();return step;}///判断是否点击在控制点上Step? _isHitControlPoint(Offset src) {for (Step step in drawList) {if (_distance(step.pointRight, src) < hitRadius) {step.hitPointType = PointType.pointRight;return step;} else if (_distance(step.pointLeft, src) < hitRadius) {step.hitPointType = PointType.pointLeft;return step;}}return null;}///判断是否点击在连接点上Step? _isHitDrawPoint(Offset src) {for (Step step in drawList) {if (_distance(step.point, src) < hitRadius) {step.hitPointType = PointType.point;return step;}}return null;}///更新点信息_update(Step hitStep, Offset target) {if (hitStep.hitPointType == PointType.pointRight) {hitStep.pointRight = target;if (isBind) {hitStep.pointLeft = hitStep.point.scale(2, 2) - target;}} else if (hitStep.hitPointType == PointType.pointLeft) {hitStep.pointLeft = target;if (isBind) {hitStep.pointRight = hitStep.point.scale(2, 2) - target;}} else if (hitStep.hitPointType == PointType.point) {hitStep.pointLeft = hitStep.pointLeft - hitStep.point + target;hitStep.pointRight = hitStep.pointRight - hitStep.point + target;hitStep.point = target;}}///两点距离double _distance(Offset one, Offset two) {return (one - two).distance;}///撤销、后退_undo() {Step? step = stackHistory.pop();if (step != null) {drawList.remove(step);stackRecycleBin.push(step);}}///重做、前进_redo() {Step? step = stackRecycleBin.pop();if (step != null) {drawList.add(step);stackHistory.push(step);}}///选择、完成_select() {}///反选、完成_invert() {}///删除_delete() {}///加载图片Future<Image> _load(String url) async {ByteData data = await NetworkAssetBundle(Uri.parse(url)).load(url);Codec codec = await instantiateImageCodec(data.buffer.asUint8List());FrameInfo fi = await codec.getNextFrame();return fi.image;}
}class TestDraw extends CustomPainter {static double width = 260;static double width1 = 50;static double height1 = 100;///绘制集合final List<Step> draw;///背景图片final Image? image;Step? tempStep;Step? tempFirstStep;TestDraw(this.image, this.draw);@overridevoid paint(Canvas canvas, Size size) {///绘制背景if (image != null) {canvas.drawImageRect(image!,Rect.fromLTRB(0,0,image!.width.toDouble(),image!.height.toDouble(),),Rect.fromLTRB(width1,height1,width + width1,width * image!.height / image!.width + height1,),Paint(),);}if (draw.isNotEmpty) {///构建画点与点之间的连线的pathPath path = Path();///绘制点和线for (int i = 0; i < draw.length; i++) {Step step = draw[i];if (!step.isLast) {canvas.drawCircle(step.point, 4.r, Paint()..color = Colors.red);canvas.drawCircle(step.pointLeft, 4.r, Paint()..color = Colors.purple);canvas.drawCircle(step.pointRight, 4.r, Paint()..color = Colors.purple);///画控制点和连线点之间的线段canvas.drawLine(step.point,step.pointLeft,Paint()..color = Colors.green..style = PaintingStyle.stroke);canvas.drawLine(step.point,step.pointRight,Paint()..color = Colors.green..style = PaintingStyle.stroke);}///构建画点与点之间的连线的pathif (step.isLast) {if (tempFirstStep != null && tempStep != null) {path.cubicTo(tempStep!.pointRight.dx,tempStep!.pointRight.dy,tempFirstStep!.pointLeft.dx,tempFirstStep!.pointLeft.dy,tempFirstStep!.point.dx,tempFirstStep!.point.dy,);}} else {//处理初始点if (step.isFirst) {tempFirstStep = step;path.moveTo(step.point.dx, step.point.dy);}if (tempStep != null) {path.cubicTo(tempStep!.pointRight.dx,tempStep!.pointRight.dy,step.pointLeft.dx,step.pointLeft.dy,step.point.dx,step.point.dy,);}}tempStep = step;}if (draw.length >= 2) {canvas.drawPath(path,Paint()..color = Colors.red..style = PaintingStyle.stroke..strokeWidth = 1.5,);}}}@overridebool shouldRepaint(covariant CustomPainter oldDelegate) {return true;}
}class Step {///线条连接点Offset point;///右控制点Offset pointRight;///左控制点(起始点没有左控制点的)Offset pointLeft;///是否选中了点的类型PointType hitPointType = PointType.pointRight;///是否是第一个控制点bool isFirst = false;///是否是最后一个控制点bool isLast = false;Step(this.point,this.pointRight,this.pointLeft,);
}///点类型
enum PointType {///线条连接点point,///右控制点pointRight,///左控制点pointLeft
}

stack代码:

///栈,先进后出
class KqStack<T> {final List<T> _stack = [];///入栈push(T obj) {_stack.add(obj);}///出栈T? pop() {if (_stack.isEmpty) {return null;} else {return _stack.removeLast();}}///栈长度length() {return _stack.length;}///清除栈clear() {_stack.clear();}
}

主要思路:

更具手指点击屏幕的位置,记录点击的位置,并生成绘制点和两个控制点,手指拖动控制点时,动态刷新控制点位置,然后利用flutter绘制机制,在canvas上根据点的位置和控制点的位置绘制三阶贝塞尔曲线,实现钢笔工具效果。具体实现可以看代码,有注释,逻辑应该还算清晰。

相关文章:

Flutter实现PS钢笔工具,实现高精度抠图的效果。

演示&#xff1a; 代码&#xff1a; import dart:ui;import package:flutter/material.dart hide Image; import package:flutter/services.dart; import package:flutter_screenutil/flutter_screenutil.dart; import package:kq_flutter_widgets/widgets/animate/stack.dart…...

苏宁滑块验证

网址&#xff1a;https://passport.suning.com/ids/login总结一下&#xff0c;别被他的表面现象给骗了&#xff0c;这玩意儿&#xff0c;个人认为&#xff0c;腾讯的都没法跟他比&#xff01;&#xff01;&#xff01; 难点&#xff1a;动态混淆&#xff0c;vmp&#xff0c;图片…...

c语言。。。

gcc thread.c -lpthread -o app -fexec-charsetgbkthread.c为当前目录下编写的c代码 代码中引入了<pthread.h>线程库&#xff0c;所以要加上-lpthread -o app 输出.exe的c可执行文件&#xff0c;文件名为app -fexec-charsetgbk 设置编码方式&#xff0c;防止控制台输出中…...

vue-cli创建项目、vue项目目录结(运行vue项目)、ES6导入导出语法、vue项目编写规范

vue-cli创建项目、vue项目目录结构、 ES6导入导出语法、vue项目编写规范 1 vue-cli创建项目 1.1 vue-cli 命令行创建项目 1.2 使用vue-cli-ui创建 2 vue项目目录结构 2.1 运行vue项目 2.2 vue项目的目录结构 3 es6导入导出语法 4 vue项目编写规范 4.1 修改项目 4.2 以后…...

QT读取DLL加载算法

有这样一个场景&#xff0c;我有一个GUI软件&#xff0c;把他想象成PS软件&#xff0c;集成了很多工具。现在我要添加新算法(PS工具)&#xff0c;该怎么办&#xff1f; 有三种办法&#xff1a; 第一种我把新算法代码加到项目中&#xff0c;编译整个项目。 第二种&#xff0c;新…...

HTTPX-用于Python的下一代HTTP客户端

1、前言 在使用 Python 进行接口自动化时&#xff0c;大多数都会使用 requests 模块&#xff0c;requests 是一个常用的 HTTP 请求库&#xff0c;可以方便地向网站发送 HTTP 请求&#xff0c;并获取响应结果。 本篇将介绍 Python 的下一代 HTTP 客户端 - HTTPX 2、简介 HTT…...

[LLM+AIGC] 01.应用篇之中文ChatGPT初探及利用ChatGPT润色论文对比浅析(文心一言 | 讯飞星火)

近年来&#xff0c;人工智能技术火热发展&#xff0c;尤其是OpenAI在2022年11月30日发布ChatGPT聊天机器人程序&#xff0c;其使用了Transformer神经网络架构&#xff08;GPT-3.5&#xff09;&#xff0c;能够基于在预训练阶段所见的模式、统计规律和知识来生成回答&#xff0c…...

OCR -- 文本检测

目标检测&#xff1a; 不仅要解决定位问题&#xff0c;还要解决目标分类问题&#xff0c;给定图像或者视频&#xff0c;找出目标的位置&#xff08;box&#xff09;&#xff0c;并给出目标的类别&#xff1b; 文本检测&#xff1a; 给定输入图像或者视频&#xff0c;找出文本的…...

【系统架构】软件可靠性基础知识

导读&#xff1a;本文整理关于软件可靠性基础知识构建系统架构知识体系。完整和扎实的系统架构知识体系是作为架构设计的理论支撑&#xff0c;基于大量项目实践经验基础上&#xff0c;不断加深理论体系的理解&#xff0c;从而能够创造新解决系统相关问题。 目录 1、软件可靠性…...

相机Camera

Camera需与SurfaceView配合使用 Camera类常用方法&#xff1a; Camera.open() 创建Camera实例&#xff0c;打开相机 getParameters() 获取相机参数 release() 释放相机资源 setParameters(Camera.Parameters parameters) 设置相机参数 setPreviewDisplay(SurfaceHolder holde…...

洛谷P8815:逻辑表达式 ← CSP-J 2022 复赛第3题

【题目来源】https://www.luogu.com.cn/problem/P8815https://www.acwing.com/problem/content/4733/【题目描述】 逻辑表达式是计算机科学中的重要概念和工具&#xff0c;包含逻辑值、逻辑运算、逻辑运算优先级等内容。 在一个逻辑表达式中&#xff0c;元素的值只有两种可能&a…...

ElementUI实现登录注册+axios全局配置+CORS跨域

一、搭建项目 1.1 安装 Element-UI 先确保是否安装了vue-cli脚手架工具 !!! 安装vue脚手架可以看看我的上一篇博客 构建好项目后通过npm安装element-ui cd 项目根路径 #进入新建项目的根目录 npm install element-ui -S #安装…...

Vue 07 Vue中的数据代理

通过数据代理&#xff0c;我可以方便的使用vm.属性&#xff0c;修改data中的属性 什么是数据代理 数据代理&#xff1a;通过一个对象代理对另一个对象中属性的操作&#xff08;读/写&#xff09; 我们修改obj2的x属性&#xff0c;其实修改的是obj的x属性 <!DOCTYPE html&…...

Foxit PDF SDK Windows 9.1 Crack

Foxit PDF SDK 变更日志 Windows/Linux/Mac 2023 年 8 月 新功能/增强功能 在开始签名之前设置外观。支持使用共享字典添加签名。允许在调用 Signature::StartSign() 之前增量保存文档。在签名前修改现有未签名分页印章签名的外观。支持使用共享字典添加分页签名。忽略全角…...

UG NX二次开发(C++)-采用NXOpen方法计算体的质心

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、创建一个part文件3、测量质心的NXOpen方法3.1 方法说明3.2 质心测量的代码3.3 测试结果1、前言 在UG NX二次开发过程中,测量是一个很必要的功能,比如测量距离、角度、面的体积、边长、…...

Java代码审计17之fastjson反序列化漏洞(2)

文章目录 1、类加载与反射调用1.1、类加载1.2、测试代码1.3、通过类的加载和反射调用evil类 2、Fastjson TemplatesImpl链调试2.1、链路总览2.2、调试构造利用链 3、fastjson反序列化TemplatesImpl 利⽤3.1、开启 Feature.SupportNonPublicField 得作用3.2、构造利用payload3.3…...

Fork/Join 框架是干什么的?

Fork/Join框架是Java中用于并行计算的一个重要工具,它旨在简化多线程编程,特别适用于分治任务的并行执行。Fork/Join框架的主要目标是提高多核处理器上任务的并行性,从而加速计算。 Fork/Join框架的核心概念包括以下几个要点: 分治策略:Fork/Join框架基于分治策略,将一个…...

电子信息工程专业课复习知识点总结:(五)通信原理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 第一章通信系统概述——通信系统的构成、各部分性质、性能指标1.通信系统的组成&#xff1f;2.通信系统的分类&#xff1f;3.调制、解调是什么&#xff1f;有什么用…...

LeetCode算法二叉树—二叉树的中序遍历

目录 94. 二叉树的中序遍历 - 力扣&#xff08;LeetCode&#xff09; 代码&#xff1a; 运行结果&#xff1a; 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&am…...

ubuntu 18.04 中 eBPF samples/bpf 编译

1. history 信息 一次成功编译 bpf 后执行 history 得到的信息&#xff1a; yingzhiyingzhi-Host:~/ex/ex_kernel/linux-5.4$ history1 ls2 mkdir ex3 cd ex4 mkdir ex_kernel5 ls /boot/6 sudo apt install linux-source7 ls /usr/src/8 uname -r9 cd ex_kernel/10…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...