Qt贝塞尔曲线

news2025/1/16 5:43:11

目录

  • 引言
  • 核心代码
    • 基本表达
    • 绘制曲线
    • 使用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.coloradmin.cn/o/1200218.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SPC 的一些小知识

在生产管理系统种&#xff0c;经常回涉及到质量管理分&#xff0c;我们经常听说SPC、SPC控制图等和SPC相关的词汇&#xff0c;那么SPC是什么意思呢&#xff1f;它有什么作用呢&#xff1f;在这里通俗一点介绍一下SPC。 SPC是统计过程控制&#xff08;Statistical Process Cont…

xsschallenge通关攻略详解

xsschallenge通过攻略 文章目录 xsschallenge通过攻略第一关第二关第三关第四关第五关第六关第七关第八关第九关第十关第十一关第十二关第十三关 简述 xsschallenge挑战攻略 ps: 终极测试代码 <sCr<ScRiPt>IPT>OonN"\/(hrHRefEF)</sCr</ScRiPt>IPT&g…

系列八、Mybatis一对多查询,只查询出了一条记录

一、Mybatis一对多查询&#xff0c;只查询出了一条记录 1.1、问题说明 典型的权限管理框架的数据库表中&#xff0c;一般会存在这样3种角色的表&#xff0c;即用户表、角色表、用户角色关联表&#xff0c;表设计好之后&#xff0c;往这三张表中初始化了一些测试数据&#xff0…

LOW-POWER AUDIO KEYWORD SPOTTING USING TSETLIN MACHINES

基于TM的低功耗语音关键字识别 摘要1介绍2TM的介绍3KWS的音频预处理技术4实验结果MFC4.1C设置分位数数量4.3增加关键词数量4.4 声音相似的关键词4.5 每个类别的子句数量对KWS-TM的比较学习收敛和复杂性分析 摘要 在本文中&#xff0c;我们探讨了一种基于TM的关键词识别&#x…

【MySQL系列】第二章 · SQL(上)

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

【云备份项目两万字总结】服务端篇 -----附源码

项目总结 整体回顾逐步实现utill.hppconfig.hppdata.hpphot.hppservice.hpp 代码 整体回顾 服务端的目标是&#xff1a; 对客户端的请求进行处理管理客户端上传的文件 于客户端进行数据交换&#xff0c;我们需要引入网络&#xff0c;所以我们引入第三方库----httplib.h库&am…

【Git】Git分支与应用分支Git标签与应用标签

一&#xff0c;Git分支 1.1 理解Git分支 在 Git 中&#xff0c;分支是指一个独立的代码线&#xff0c;并且可以在这个分支上添加、修改和删除文件&#xff0c;同时作为另一个独立的代码线存在。一个仓库可以有多个分支&#xff0c;不同的分支可以独立开发不同的功能&#xff0…

劲松HPV防治诊疗中心发布:HPV感染全面防治解决方案

在当今社会&#xff0c;HPV(人乳头瘤病毒)感染问题已成为广大公众关注的焦点。作为一种高度传染性的病毒&#xff0c;HPV感染不仅可能导致生殖器疣&#xff0c;还可能引发各种癌症。面对这一严重威胁&#xff0c;劲松HPV防治诊疗中心以其专业的医疗团队、正规的治疗流程和全方位…

操作系统(二)内存管理的基础知识

文章目录 前言内存管理地址空间与地址生成连续内存分配内存碎片连续分配算法碎片整理 非连续内存分配虚拟内存管理虚拟内存地址内存分段内存分页段页式内存管理虚拟内存的覆盖技术虚拟内存的交换技术 缺页异常内存页面置换算法局部页面置换算法Belady现象全局页面置换算法抖动和…

【蓝桥杯选拔赛真题66】Scratch画图机器人 少儿编程scratch图形化编程 蓝桥杯创意编程选拔赛真题解析

目录 scratch画图机器人 一、题目要求 编程实现 二、案例分析 1、角色分析

云原生 黑马Kubernetes教程(K8S教程)笔记——第一章 kubernetes介绍——Master集群控制节点、Node工作负载节点、Pod控制单元

参考文章&#xff1a;kubernetes介绍 文章目录 第一章 kubernetes介绍1.1 应用部署方式演变传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上虚拟化部署&#xff1a;可以在一台物理机上运行多个虚拟机&#xff0c;每个虚拟机都是独立的一个环境&…

VUE Slot

在某些场景中&#xff0c;我们可能想要为子组件传递一些模板片段&#xff0c;让子组件在它们的组件中渲染这些片段. <template><h3>ComponentA</h3><ComponentB><h3>插槽传递视图内容</h3></ComponentB> </template> <scr…

Redis04-分布式锁

目录 Redis实现分布式锁 分布式锁的工作流程 Redis实现分布式锁 Redission的watch dog Redis分布式锁的合理应用 Redis实现分布式锁 在单节点的服务器中&#xff0c;java中的synchronized机制是处于JVM层面的&#xff0c;只能保证线程之间的同步。而实际的服务部署中&…

Spring面试题:(六)Spring注解开发原理

ioc过程 发现只要将bean注册到BeanDefinitionMap中就可以创建bean对象 如何将xml配置的bean注册到BeanDefinitionMap 通过注解注册的bean过程一样 注册bean的接口&#xff1a;BeanDefinitionRegistryPostProcessor 开启组件扫描的两种方式&#xff1a;xml和注解 xml方式…

Unity之NetCode多人网络游戏联机对战教程(8)--玩家位置同步

文章目录 前言添加相机玩家添加对应组件服务端权威&#xff08;server authoritative&#xff09;客户端权威&#xff08;client authoritative&#xff09;服务端同步位置阅读与理解PlayerTransformSync.csNetworkVariableUploadTransformSyncTransform 后话 前言 承接上篇&a…

vColorPicker与vue3-colorPicker——基于 Vue 的颜色选择器插件

文章目录 前言样例特点 一、使用步骤&#xff1f;1. 安装2.引入3.在项目中使用 vcolorpicker 二、选项三、事件四、问题反馈问题所在安装引入例子效果图 前言 vColorPicker——官网 vColorPicker——GitHub 样例 vColorPicker是基于 Vue 的一款颜色选择器插件&#xff0c;仿照…

主题模型LDA教程:主题数选取 困惑度perplexing

文章目录 LDA主题数困惑度1.概率分布的困惑度2.概率模型的困惑度3.每个分词的困惑度 LDA主题数 LDA作为一种无监督学习方法&#xff0c;类似于k-means聚类算法&#xff0c;需要给定超参数主题数K&#xff0c;但如何评价主题数的优劣并无定论&#xff0c;一般采取人为干预、主题…

电子工程师的焊接技法总结

基础学习视频如下&#xff1a; 1 老司机焊接纯干货分享&#xff0c;让你焊接不迷路&#xff0c;很适合零基础小白_哔哩哔哩_bilibili 焊接常用工具 1 焊锡丝 按照粗细来分的话&#xff0c;有粗焊锡&#xff0c;有细焊锡&#xff0c;细焊锡一般适合比较精细的焊接。 按照是否含铅…

吃透 Spring 系列—Web部分

目录 ◆ Spring整合web环境 - Javaweb三大组件及环境特点 - Spring整合web环境的思路及实现 - Spring的web开发组件spring-web ◆ web层MVC框架思想与设计思路 ◆ Spring整合web环境 - Javaweb三大组件及环境特点 在Java语言范畴内&#xff0c;web层框架都是基于J…