我的贝塞尔曲线正方形的边长公式是什么?

编程语言 2026-07-09

我正在用Cairo做一个边缘带虚线且圆角的按钮。Cairo要求将虚线段的长度以像素为单位,而不是整条线的比例,所以我尝试计算用于角的曲线的长度。然而,我的公式似乎不对。

以下是我尝试做的一个简化版本:

#include <cairo.h>
#include <cairo-svg.h>

constexpr double button_size = 192;
constexpr double button_thickness = 8;
/*
 * 1.6232252401402305
 *     ⌠1  _____________
 *   ≈ ⌡0 √(2t)²+(2−2t)²
 */
constexpr double side_length = 1.6232252401402305 * 0.5 * (button_size - button_thickness);

int main() {
    cairo_surface_t *surface = cairo_svg_surface_create("./test.svg", button_size, button_size);
    cairo_t *ctx = cairo_create(surface);
    cairo_set_source_rgba(ctx, 0.85, 0.85, 0.85, 1);
    cairo_set_dash(ctx, (double []){side_length / 8}, 1, side_length / 16);
    constexpr double lt = button_thickness / 2;
    constexpr double rt = button_size - button_thickness / 2;
    constexpr double tp = lt;
    constexpr double bm = rt;
    constexpr double mx = button_size / 2;
    constexpr double my = mx;
    cairo_set_line_width(ctx, button_thickness);
    cairo_move_to(ctx, lt, my);
    cairo_curve_to(ctx, lt, tp, lt, tp, mx, tp);
    cairo_curve_to(ctx, rt, tp, rt, tp, rt, my);
    cairo_curve_to(ctx, rt, bm, rt, bm, mx, bm);
    cairo_curve_to(ctx, lt, bm, lt, bm, lt, my);
    cairo_close_path(ctx);
    cairo_stroke(ctx);
    cairo_destroy(ctx);
    cairo_surface_destroy(surface);
    return 0;
}

我想要的不是16段虚线,而是17¾ 段。在边长公式中,我没有减去按钮厚度,试过不减去它,以及将其减去两次,但都不起作用。我的数学算错了吗,还是这就是Cairo的工作方式?这里边长的正确公式是什么?

更新: 通过实测,1.8020878相比1.6232252401402305更接近实际系数,以至于在100×放大时我都察觉不到误差。为什么会这样?这个数字来自哪里?

解决方案

主要问题在于你以为一个两个中间点相等的三次贝塞尔曲线等同于一条二次曲线。事实并非如此。要把二次曲线转换成三次曲线,需要把中间点朝离它最近的端点方向移动三分之一的距离。

在这个例子中,这意味着将

<path d="
    M 0 0
    Q 0 1; 1 1
" />

转换为大约

<path d="
    M 0 0
    C 0 0.67; 0.33 1; 1 1
" />

(不过在我的例子中,我觉得把两个中间点放在同一个位置的原始形状更好。)

另外一个问题是三次贝塞尔曲线比较难。没有一般的封闭形式表达长度,因此Cairo必须进行近似。这意味着即使你使用了正确的形状,数值在数学上是正确的,Cairo仍然不会正确绘制这条线,因为它不知道实际的长度。要得到Cairo认为的长度,你可以使用 cairo_copy_path_flat 将曲线转化为近似曲线,然后像下面这样遍历返回的路径:

double getPathLength(cairo_t *const restrict ctx) {
    cairo_path_t *const path = cairo_copy_path_flat(ctx);
    double start_x = NAN, start_y = NAN, curr_x = NAN, curr_y = NAN;
    double length = 0;
    for (int i = 0; i < path->num_data; i += path->data[i].header.length) {
        cairo_path_data_t *data = &path->data[i];
        switch (data->header.type) {
            case CAIRO_PATH_MOVE_TO:
                curr_x = start_x = data[1].point.x;
                curr_y = start_y = data[1].point.y;
                break;
            case CAIRO_PATH_LINE_TO: {
                const double x = data[1].point.x, y = data[1].point.y;
                const double rx = x - curr_x, ry = y - curr_y;
                length += sqrt(rx * rx + ry * ry);
                curr_x = x; curr_y = y;
                break;
            }
            case CAIRO_PATH_CLOSE_PATH: {
                const double rx = start_x - curr_x, ry = start_y - curr_y;
                length += sqrt(rx * rx + ry * ry);
                curr_x = start_x; curr_y = start_y;
            }
            default:
        }
    }
    cairo_path_destroy(path);
    return length;
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章