Qt贝塞尔曲线

目录

  • 引言
  • 核心代码
    • 基本表达
    • 绘制曲线
    • 使用QEasingCurve
  • 完整代码

引言

贝塞尔曲线客户端开发中常见的过渡效果,如界面的淡入淡出、数值变化、颜色变化等等。为了能够更深的了解地理解贝塞尔曲线,本文通过Demo将贝塞尔曲线绘制出来,如下所示:

在这里插入图片描述

核心代码

基本表达

一般来说贝塞尔曲线由起止点以及c1、c2点构成,如上图中,黄色为c1点,绿色为c2点,通过调整c1、c2点去调整整个曲线的变化快慢。

cubic-bezier(.42,0,.58,1)

而这两个点这么怎么去表达呢,如上所示,可以拆分为c1坐标(0.42,0)和c2坐标(0.58,1),而这个坐标则是一个相对坐标,范围是从(0,0)到(1,1),如下图所示:
在这里插入图片描述

绘制曲线

void QPainterPath::cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint)
Adds a cubic Bezier curve between the current position and the given endPoint using the control points specified by c1, and c2.
After the curve is added, the current position is updated to be at the end point of the curve.

绘制可以通过QPainterPath::cubicTo完成,需要注意的是其中使用的是这个绘制界面的绝对坐标,而不是相对坐标,因此需要增加两个转换函数方便后续的编码。

首先是百分比坐标转换为实际坐标:

QPoint CubicBezierWidget::PercentToPosition(const QPointF &percent)
{
    return valid_rect_.bottomLeft() + QPoint(valid_rect_.width() * percent.x(), -valid_rect_.height() * percent.y());
}

再者就是将实际坐标转换为百分比坐标:

QPointF CubicBezierWidget::PositionToPercent(const QPoint &position)
{
    double x_percent = position.x() - valid_rect_.bottomLeft().x();
    x_percent = x_percent / valid_rect_.width();

    double y_percent = valid_rect_.bottomLeft().y() - position.y();
    y_percent = y_percent / valid_rect_.height();

    return QPointF(x_percent, y_percent);
}

最后则是起止点以及c1、c2组装起来

    // 关键数据
    QPointF start_point = valid_rect_.bottomLeft();
    QPointF end_point = valid_rect_.topRight();
    QPoint c1_point = PercentToPosition(c1_);
    QPoint c2_point = PercentToPosition(c2_);

    QPainterPath path;
    path.moveTo(start_point);
    path.cubicTo(c1_point, c2_point, end_point);

使用QEasingCurve

QEasingCurve是Qt核心库的曲线函数,可以使用其作为动画函数的变化曲线,这也是贝塞尔曲线最多的应用场景,通过QAbstractAnimation::setEasingCurve设置。此处为了展示,从曲线中采样10个点,通过函数QEasingCurve::valueForProgress获取对应的y值进行绘制,如下所示:

    QEasingCurve easing_curve(QEasingCurve::BezierSpline);
    easing_curve.addCubicBezierSegment(QPointF(0.42, 0.0), QPointF(0.58, 1.0), QPointF(1.0, 1.0));

    QPainterPath path_bezier;
    int count = 10;
    for(int i=0; i <= count; i++){
        double progress = (double)i / count;
        QPointF target_point(PercentToPosition(QPointF(progress, easing_curve.valueForProgress(progress))));
        if(i){
            path_bezier.lineTo(target_point);
        }
        else{
            path_bezier.moveTo(target_point);
        }
    }
    
    pen.setColor(QColor(241, 148, 138));
    painter.save();
    painter.setPen(pen);
    painter.setBrush(Qt::NoBrush);
    painter.drawPath(path_bezier);
    painter.restore();

完整代码

class CubicBezierWidget : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(QPointF c1 READ c1 WRITE setC1 NOTIFY c1Changed FINAL)
    Q_PROPERTY(QPointF c2 READ c2 WRITE setC2 NOTIFY c2Changed FINAL)

public:
    explicit CubicBezierWidget(QWidget *parent = nullptr);

    enum MouseState {
        MouseNormal = 0,
        MouseActivatedC1,
        MouseActivatedC2,
    };

public:
    QPointF c1() const;
    void setC1(QPointF c1);

    QPointF c2() const;
    void setC2(QPointF c2);

signals:
    void c1Changed();
    void c2Changed();

protected:
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;

    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;

private:
    QPoint PercentToPosition(const QPointF &percent);
    QPointF PositionToPercent(const QPoint &position);

    MouseState StateByPosition(const QPoint &position);

private slots:
    void toUpdate();

private:
    int space_ = 20;
    int radius_ = 12;
    int inner_radius_ = 6;

    // 百分比
    QPointF c1_;
    QPointF c2_;
    QRect valid_rect_;

    // 鼠标标志
    MouseState mouse_type_ = MouseNormal;
};
#include <QDebug>
#include <QPainter>
#include <QPainterPath>
#include <QPaintEvent>
#include <QEasingCurve>

CubicBezierWidget::CubicBezierWidget(QWidget *parent)
    : QWidget{parent}
{
    connect(this, &CubicBezierWidget::c1Changed, this, &CubicBezierWidget::toUpdate);
    connect(this, &CubicBezierWidget::c2Changed, this, &CubicBezierWidget::toUpdate);
}

void CubicBezierWidget::paintEvent(QPaintEvent *event)
{
    // 关键数据
    QPointF start_point = valid_rect_.bottomLeft();
    QPointF end_point = valid_rect_.topRight();
    QPoint c1_point = PercentToPosition(c1_);
    QPoint c2_point = PercentToPosition(c2_);

    QPainterPath path;
    path.moveTo(start_point);
    path.cubicTo(c1_point, c2_point, end_point);

    // 初始化画笔
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::NoBrush);

    // 背景
    painter.save();
    painter.setBrush(QColor(32, 32, 32));
    painter.drawRect(event->rect());
    painter.setBrush(QColor(42, 42, 42));
    painter.drawRect(valid_rect_);
    painter.restore();

    QPen pen;
    pen.setCapStyle(Qt::RoundCap);
    pen.setWidth(4);
    pen.setColor(QColor(100, 100, 100));

    // 连接线
    painter.save();
    painter.setPen(pen);
    painter.drawLine(start_point, c1_point);
    painter.drawLine(end_point, c2_point);
    painter.restore();

    pen.setWidth(6);
    pen.setColor("white");
    // 曲线
    painter.save();
    painter.setPen(pen);
    painter.drawPath(path);
    painter.restore();

    // 操作圆c1
    painter.save();
    painter.setBrush(QColor(247, 220, 111));
    painter.drawEllipse(c1_point, radius_, radius_);
    painter.setBrush(Qt::white);
    painter.drawEllipse(c1_point, inner_radius_, inner_radius_);
    painter.restore();

    // 操作圆c2
    painter.save();
    painter.setBrush(QColor(72, 201, 176));
    painter.drawEllipse(c2_point, radius_, radius_);
    painter.setBrush(Qt::white);
    painter.drawEllipse(c2_point, inner_radius_, inner_radius_);
    painter.restore();
}

void CubicBezierWidget::resizeEvent(QResizeEvent *event)
{
    valid_rect_ = rect().adjusted(space_, space_, -space_, -space_);

    // 还原
    setC1(QPointF(0, 0));
    setC2(QPointF(1, 1));
}

void CubicBezierWidget::mousePressEvent(QMouseEvent *event)
{
    mouse_type_ = StateByPosition(event->pos());
}

void CubicBezierWidget::mouseReleaseEvent(QMouseEvent *event)
{
    mouse_type_ = MouseNormal;
}

void CubicBezierWidget::mouseMoveEvent(QMouseEvent *event)
{
    if(mouse_type_ == MouseActivatedC1){
        QPointF percent = PositionToPercent(event->pos());
        setC1(percent);
    }
    else if(mouse_type_ == MouseActivatedC2){
        QPointF percent = PositionToPercent(event->pos());
        setC2(percent);
    }
}

QPoint CubicBezierWidget::PercentToPosition(const QPointF &percent)
{
    return valid_rect_.bottomLeft() + QPoint(valid_rect_.width() * percent.x(), -valid_rect_.height() * percent.y());
}

QPointF CubicBezierWidget::PositionToPercent(const QPoint &position)
{
    double x_percent = position.x() - valid_rect_.bottomLeft().x();
    x_percent = x_percent / valid_rect_.width();

    double y_percent = valid_rect_.bottomLeft().y() - position.y();
    y_percent = y_percent / valid_rect_.height();

    return QPointF(x_percent, y_percent);
}

CubicBezierWidget::MouseState CubicBezierWidget::StateByPosition(const QPoint &position)
{
    QPoint c2_position = PercentToPosition(c2_);
    QRect c2_rect(c2_position.x() - radius_, c2_position.y() - radius_, 2 * radius_, 2* radius_);
    if(c2_rect.contains(position)){
        return MouseActivatedC2;
    }

    QPoint c1_position = PercentToPosition(c1_);
    QRect c1_rect(c1_position.x() - radius_, c1_position.y() - radius_, 2 * radius_, 2* radius_);
    if(c1_rect.contains(position)){
        return MouseActivatedC1;
    }

    return MouseNormal;
}

void CubicBezierWidget::toUpdate()
{
    update();
}

QPointF CubicBezierWidget::c1() const
{
    return c1_;
}

void CubicBezierWidget::setC1(QPointF c1)
{
    c1.setX(qBound(0.0, c1.x(), 1.0));
    c1.setY(qBound(0.0, c1.y(), 1.0));

    if (c1_ == c1)
        return;
    c1_ = c1;
    emit c1Changed();
}

QPointF CubicBezierWidget::c2() const
{
    return c2_;
}

void CubicBezierWidget::setC2(QPointF c2)
{
    c2.setX(qBound(0.0, c2.x(), 1.0));
    c2.setY(qBound(0.0, c2.y(), 1.0));

    if (c2_ == c2)
        return;
    c2_ = c2;
    emit c2Changed();
}

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

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

相关文章

红黑树插入节点的模拟实现

要学习红黑树节点的插入那么首先就要了解什么是红黑树&#xff0c;以及红黑树的特点。 红黑树的特点 本来AVL树已经很厉害了&#xff0c;但是红黑树的总体效率略比1AVL树高。高的大体原因。我们先来看一下红黑树和AVL树的区别。 AVL树严格的保证了左子树和右子树的高度差不超…

计算机网络——b站王道考研笔记

第一章 计算机网络体系结构 1.计算机网络概述 &#xff08;1&#xff09;概念 计算机网络是一个将分散的&#xff0c;具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功能完善的软件实现资源共享和信息传递的系统&#xff1b; 是互连的&#…

kubernetes--pod详解

目录 一、pod简介&#xff1a; 1. Pod基础概念&#xff1a; 2. Kubrenetes集群中Pod的两种使用方式&#xff1a; 3. pod资源中包含的容器&#xff1a; 4. pause容器的两个核心功能&#xff1a; 5. Kubernetes中使用pause容器概念的用意&#xff1a; 二、pod的分类&#xff1a…

SharePoint 页面中插入自定义代码

我们都知道 SharePoint 是对页面进行编辑的。 对于一些有编程基础的人来说&#xff0c;可能需要对页面中插入代码&#xff0c;这样才能更好的对页面进行配置。 但是在新版本的 SharePoint modern 页面来说&#xff0c;虽然我们可以插入 Embed 组件。 但是 Embed 组件中是不允…

Linux系统上搭建高可用Kafka集群(使用自带的zookeeper)

本次在CentOS7.6上搭建Kafka集群 Apache Kafka 是一个高吞吐量的分布式消息系统&#xff0c;被广泛应用于大规模数据处理和实时数据管道中。本文将介绍在CentOS操作系统上搭建Kafka集群的过程&#xff0c;以便于构建可靠的消息处理平台。 文件分享&#xff08;KafkaUI、kafka…

Frp内网穿透部署

Frp内网穿透部署记录windows为例 A固定外网IP服务器一台&#xff08;可以映射端口&#xff09;B内网PC一台&#xff0c;可上外网 A固定外网IP服务器一台&#xff08;可以映射端口&#xff09; B内网PC一台&#xff0c;可上外网 GO语言&#xff1a;https://golang.org/doc/ins…

【算法专题】双指针—三数之和

力扣题目链接&#xff1a;三数之和 一、题目解析 二、算法原理 解法一&#xff1a;排序暴力枚举利用set去重 代码就不写了&#xff0c;你们可以试着写一下 解法二&#xff1a;排序双指针 这题和上一篇文章的两数字和方法类似 排序固定一个数a在这个数的后面区间&#xff0…

Spring Bean 的生命周期

一、前言&#xff1a; Spring Bean 的生命周期之前先来了解两个概念&#xff1a; 1.1 什么是 Bean In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is in…

“面向目标值的排列匹配“和“面向目标值的背包组合问题“的区别和leetcode例题详解

1 目标值排列匹配 1.1 从目标字符串的角度来看&#xff0c;LC139是一个排列问题&#xff0c;因为最终目标子串的各个字符的顺序是固定的&#xff1f; 当我们从目标字符串 s 的角度来看 LC139 “单词拆分” 问题&#xff0c;确实可以认为它涉及到排列的概念&#xff0c;但这种…

C++ Qt 学习(四):自定义控件与 qss 应用

1. qss 简介 Qt style sheet&#xff08;qss&#xff0c;Qt 样式表&#xff09;&#xff0c;不需要用 C 代码控件进行重载&#xff0c;就可以修改控件外观&#xff0c;类似于前端的 css 2. qss 选择器 2.1 通配符选择器 /* 设置后控件窗口背景色都被修改为黄色 */ * {backg…

便捷Benchmark.sh 自动匹配workload(自用)

​ 因为db_bench选项太多&#xff0c;而测试纬度很难做到统一&#xff08;可能一个memtable大小的配置都会导致测试出来的写性能相关的的数据差异很大&#xff09;&#xff0c;所以官方给出了一个benchmark.sh脚本用来对各个workload进行测试。 该脚本能够将db_bench测试结果中…

深度解析NLP定义、应用与PyTorch实战

1. 概述 文本摘要是自然语言处理&#xff08;NLP&#xff09;的一个重要分支&#xff0c;其核心目的是提取文本中的关键信息&#xff0c;生成简短、凝练的内容摘要。这不仅有助于用户快速获取信息&#xff0c;还能有效地组织和归纳大量的文本数据。 1.1 什么是文本摘要&#x…

《詩經别解》——國風·周南·雎鳩​​​​​​​

一、关于古文的一个认识 目前可以阅读的古文经典&#xff0c;大多是经历了几千年的传承。期间的武力战争、文化纷争、宗教侵袭、官僚介入及文人的私人恩怨与流派桎梏&#xff0c;印刷与制作技术&#xff0c;导致这些古文全部都已经面目全非。简单地说&#xff0c;你读到的都是…

树与二叉树作业

1. 已知一个二叉树的中序遍历序列和后序遍历序列&#xff0c;求这棵树的前序遍历序列 【问题描述】 已知一个二叉树的中序遍历序列和后序遍历序列&#xff0c;求这棵树的前序遍历序列。 【输入形式】 一个树的中序遍历序列 该树后序遍历序列&#xff0c;中间用空格分开。输…

el-table实现展开当前行时收起上一行的功能

<el-tableref"tableRef":data"tableData":expand-row-keys"expandRowKeys":row-key"handleRowKey" // 必须指定 row-keyexpand-change"handleExpandChange" // 当用户对某一行展开或者关闭的时候会触发该事件> <…

Creo螺旋扫描/弹簧画法

一&#xff1a;点击螺旋扫描 二&#xff1a;参考–》螺旋轮廓的定义&#xff1a; 三、草绘轮廓线&#xff1a;视图放正 四、草绘弹簧丝线形状&#xff1a; 在非中轴线上画圆&#xff1a; 制螺旋线&#xff1a; 首先理清Creo绘制螺旋线的逻辑&#xff08;不同于UG直接给定直径…

华为ensp:边缘端口并启动BUDU保护

如上图前提是三个交换机都做了rstp&#xff0c;则在边缘的地方做 边缘端口并启动BUDU保护&#xff0c;也就是我用绿色圈出来的地方 边缘1 进入交换机的系统视图 interface e0/0/3 进入接口 stp edged-port enable quit 再退回系统视图 stp bpdu-protection 这样就可以了…

Arduino ESP8266使用AliyunIoTSDK.h连接阿里云物联网平台

文章目录 1、AliyunIoTSDK简介2、相关库安装3、阿里云创建产品&#xff0c;订阅发布4、对开源的Arduino ESP8266源代码修改5、使用阿里云点亮一个LED灯6、设备向阿里云上传温度数据7、项目源码 1、AliyunIoTSDK简介 AliyunIoTSDK是arduino的一个库&#xff0c;可以在arduino的…

20分钟搭建Ubertooth One开源蓝牙测试工具

kali linux 2023 安装依赖&#xff08;记得使用root用户搭建环境&#xff09; 1、apt-get update 2、apt install ubertooth 更新共享库缓存 3、ldconfig 安装 Ubertooth 工具和驱动程序 4、插入Ubertooth One工具 5、ubertooth-util -v 备注&#xff1a;出现Firmwate v…

Android系统开发快速寻找代码(如何在文件夹中寻找代码)

很多时候对于Android系统开发小白而言&#xff0c;例如预置APK&#xff0c;知道了APK包名不知道具体代码位置需要去寻找代码&#xff0c;但是Android系统代码十分庞大&#xff0c;如何快速准确查询代码是个问题。 本人目前只探索到了一些方法&#xff0c;如有更有效的办法可以…
最新文章