Qt绘制椭圆曲线的角度问题(离心角和旋转角)

最近有网友遇到一个问题,使用 QPainter::drawPie 或者 QPainter::drawArc 绘制同中心的圆和椭圆,指定同一起始点和角度,两条弧线的起点与圆心的连线不在同一直线上:
在这里插入图片描述
测试代码:

QPainter painter(this);
   painter.setRenderHint(QPainter::Antialiasing);
   painter.translate(100, 100);

   QRectF ellipseRect(0, 0, 300, 200);
   QRectF circle(0, 0, 200, 200);
   circle.moveCenter(ellipseRect.center());
// 绘制圆,红色
   painter.setPen(QPen(QColor(232, 63, 63, 128), 3.0));
   painter.drawPie(circle, 10 * 16, 70 * 16);
// 绘制椭圆,绿色
   painter.setPen(QPen(QColor(63, 232, 63, 128), 3.0));
   painter.drawPie(ellipseRect, 10 * 16, 70 * 16);

1. 原理

Qt绘制椭圆曲线,使用的是椭圆离心角,参数方程是:

x = a * cosθ
y = b * sinθ

即:在椭圆的外切圆和内切圆上,选择θ角度的两个点A,B,以A点x坐标和B点y坐标,确定椭圆上一个点M。
在这里插入图片描述
因此,当以∠θ绘制时,点A、B、M、圆心O,不在一条直线上。

而通常情况下,以上图为例,以∠φ为参数调用接口,期望从M点开始绘制。需要∠φ与∠θ的转换关系。

2. 离心角与旋转角的转换关系

这里我称∠φ为旋转角,两者之间的转换公式推导参考这篇文章,这里写个结论:
(这里的θ与φ与参考文章里的反过来的)

tanθ = ( a / b) * tanφ
tanφ = ( b / a) * tanθ

再利用反三角函数计算出θ或者φ,需要注意的是,反三角函数的返回值总是在[-π/2, π/2],因此还需要进一步调整。

由上图可以推测出,∠φ和∠θ一定是在同一个象限内,因此两者的差不可能超过π/2;而且,∠θ在一、三象限时,tanθ的符号相同,所以可以通过θ′ = θ±kπ,使得θ′与φ间隔在π/2以内。

3. 转换代码

qreal convertToEccentricAngle(qreal a, qreal b, qreal angle)
{	
	// *π和π/2使用宏即可*
    static qreal pi = qDegreesToRadians(180.0);
    static qreal half_pi = qDegreesToRadians(90.0);

    // 理论上两个角度差距不会很大,且落在同一象限
    // 反函数始终在第一象限和第四象限
    qreal ret = qAtan(a / b * qTan(angle));
    if(ret > angle)
    {
        ret -= int((ret - angle) / pi) * pi;
        if(ret - angle > half_pi)
            ret -= pi;
    }
    else
    {
        ret += int((angle - ret) / pi) * pi;
        if(angle - ret > half_pi)
            ret += pi;
    }
    return ret;
}

使用该函数将指定的弧度制角转换到对应的离心角,也可以自己补充角度制的转换。

效果如下:

请添加图片描述

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
下面是一个简单的Qt绘制温度曲线的示例: ```C++ class TemperatureWidget : public QWidget { Q_OBJECT public: TemperatureWidget(QWidget *parent = nullptr) : QWidget(parent) { // 设置背景颜色 QPalette pal(palette()); pal.setColor(QPalette::Background, Qt::white); setAutoFillBackground(true); setPalette(pal); // 设置绘图区域 rect = QRect(50, 10, 300, 200); // 设置温度范围 minTemperature = -20; maxTemperature = 50; // 创建温度数据采集线程 thread = new TemperatureThread(this); // 连接数据信号和槽函数 connect(thread, &TemperatureThread::newTemperature, this, &TemperatureWidget::updateTemperature); // 启动温度数据采集线程 thread->start(); } protected: void paintEvent(QPaintEvent *event) override { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿 // 绘制坐标轴 painter.drawLine(rect.topLeft(), rect.bottomLeft()); painter.drawLine(rect.bottomLeft(), rect.bottomRight()); // 绘制温度曲线 painter.setPen(QPen(Qt::red, 2)); painter.drawPolyline(points.data(), points.size()); } private: QRect rect; // 绘图区域 double minTemperature; // 温度范围 double maxTemperature; QList<QPointF> points; // 温度数据点 TemperatureThread *thread; // 温度数据采集线程 void updateTemperature(double temperature) { // 新增温度数据点 QPointF point(points.size(), rect.bottom() - (temperature - minTemperature) * rect.height() / (maxTemperature - minTemperature)); points.append(point); // 删除超出绘图区域的温度数据点 while (points.size() > rect.width()) points.removeFirst(); // 更新UI界面 update(rect); } }; // 温度数据采集线程类 class TemperatureThread : public QThread { Q_OBJECT public: TemperatureThread(QObject *parent = nullptr) : QThread(parent) {} signals: // 定义一个新温度信号 void newTemperature(double temperature); protected: void run() override { while (!isInterruptionRequested()) { // 实时采集温度数据 double temperature = getTemperature(); // 发送新温度信号 emit newTemperature(temperature); // 休眠一段时间 msleep(1000); } } private: double getTemperature() { // 模拟温度数据采集 static double temperature = 20.0; temperature += (qrand() % 21 - 10) / 10.0; if (temperature < -20) temperature = -20; if (temperature > 50) temperature = 50; return temperature; } }; ``` 在上面的示例中,TemperatureWidget是一个温度曲线显示界面类,负责在UI界面上绘制温度曲线,并启动TemperatureThread线程进行温度数据采集。TemperatureThread是一个温度数据采集线程类,负责实时采集温度数据,并将数据通过newTemperature信号发送给UI线程。通过connect函数将newTemperature信号连接到updateTemperature槽函数,从而实现了温度曲线的实时显示。 在paintEvent函数中,首先绘制坐标轴,然后根据温度数据点绘制温度曲线。updateTemperature函数用于将新采集到的温度数据转换成图像坐标,并新增到温度数据点序列中,然后删除超出绘图区域的温度数据点,最后调用update函数刷新UI界面。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值