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

这篇具有很好参考价值的文章主要介绍了Flutter实现PS钢笔工具,实现高精度抠图的效果。。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

演示:

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

 代码:

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});

  @override
  State<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;

  @override
  void initState() {
    super.initState();
    _load("https://c-ssl.duitang.com/uploads/item/201903/19/20190319001325_bjvzi.jpg")
        .then((value) {
      _image = value;
      update();
    });
  }

  @override
  Widget 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;

                  ///判断是否hit
                  hitDrawStep = _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);

  @override
  void 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) {
      ///构建画点与点之间的连线的path
      Path 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);
        }

        ///构建画点与点之间的连线的path
        if (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,
        );
      }
    }
  }

  @override
  bool 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上根据点的位置和控制点的位置绘制三阶贝塞尔曲线,实现钢笔工具效果。具体实现可以看代码,有注释,逻辑应该还算清晰。文章来源地址https://www.toymoban.com/news/detail-730770.html

到了这里,关于Flutter实现PS钢笔工具,实现高精度抠图的效果。的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 高精度除法【c++实现】超详细讲解

    高精度算法分为两种,高精除以低精和高精除以高精。不要看都是除法,就认为原理类似,其实是有很大差距的。让我们一起来学习吧! 有句话说在前面,如果除数等于0,就不要算了,不成立。( 如果你忘了这个知识,小学数学老师饶不了你 ) 高精度除低精度,原理是模

    2024年02月13日
    浏览(21)
  • STM32 SYSTick高精度延时功能代码实现

    本篇文章将给大家讲解一下SYSTICK滴答定时器,以及讲解使用滴答定时器来实现高精度延时功能的代码。 SysTick定时器是嵌入式系统中常见的一个系统定时器,在ARM Cortex-M微控制器中广泛使用。下面是关于SysTick定时器的一些介绍: 用途: SysTick定时器通常被用作操作系统的时钟

    2024年02月22日
    浏览(35)
  • AI模型大杀器----Amazon SageMaker 实现高精度猫狗分类

    前言: Hello大家好,我是Dream。 最近受邀参与了 亚马逊云科技【云上探索实验室】 活动,基于他们的sagemaker实现了机器学习中一个非常经典的案例: 猫狗分类 。最让我惊喜的是的模型训速度比想象中 效果要好得多,而且速度十分迅速,而且总体感觉下来整个过程十分便利

    2023年04月09日
    浏览(27)
  • (基础算法)高精度加法,高精度减法

    什么叫做高精度加法呢?包括接下来的高精度减法,高精度乘法与除法都是同一个道理。正常来讲的话加减乘除,四则运算的数字都是整数,也就是需要在int的范围之内,但当这个操作数变得非常\\\"大\\\"的时候( 其实就是一个字符串,比方说有一个数是20位,如果用整数视角来

    2024年02月01日
    浏览(31)
  • 高精度算法详解

    首先要知道为什么需要高精度算法: 高精度算法是 处理大数字 的数学计算方法,当数字过大不能用 int 和 long long 存储时,我们就可以 使用string和vector类型 来存储他们的每一位,然后进行计算。 我们可以先把要输入的两个数字放到vector中存储,注意要 反着存(后边做加法

    2024年01月17日
    浏览(37)
  • 高精度加法

    高精度问题是指两个数字非常大,超过了 int ,甚至 long long 的范围,数字的位数甚至能达到 (10^5) ,那么如果要实现这样两个大数字的运算,需要解决以下两个问题: 如何存储? 这样的两个数字相加是不可能用普通类型来存储的,所以我们第一个要解决的问题就是如何存储

    2024年02月08日
    浏览(29)
  • 高精度/前缀和/差分

    存储方式: 整数的长度一般小于1e6 大整数的每一位存储到数组里 存储时低位在前,高位在后,方便进位 高精度加法 每一位相加Ai + Bi + t, t表示进位取值0/1,逢十进一 模板: 高精度减法 每一位相减Ai - Bi - t, t 表示借位取值0/1 模板: 高精度乘法 A * b ,b=10000, len(A) = 1e6 , 乘的

    2024年02月16日
    浏览(29)
  • 高精度乘法

    高精度加减法讨论的是两个大整数之间的运算。 而这里高精度乘除法讨论的是一个大整数和一个小整数之间的关系。 算法思路: 还是模拟小学的乘法列竖式,只不过此时不太一样,原本的列竖式是一位一位的乘,这里需要改变一下思路。 这里直接把小整数当成一个数,所乘

    2024年02月08日
    浏览(33)
  • 高精度延时

    在使用STM32的时候可以使用SYSTICK来实现高精度延时。 I.MX6U没有SYSTICK定时器,但是有GPT定时器来实现高精度延时。 GPT定时器是一个32位向上定时器(也就是从0x00000000开始向上递增计数), GPT定时器也可以跟一个值进行比较,当计数器值和这个值相等的话就发生比较事件,产生

    2024年02月02日
    浏览(30)
  • 高精度除法

    高精度除法和乘法讨论的一样,都是一个大整数和一个小整数之间的运算。 算法思路 根据小学除法一样,我们还是模拟这个过程。 注意这里遍历 (A) 数组的时候要按照我们读数字的顺序,也就是从数组尾部到头部遍历。 每一位的计算过程应该是,上一轮的余数 (r) 乘 (

    2024年02月08日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包