Pixel by pixel Bézier Curve

后端 未结 4 1378
萌比男神i
萌比男神i 2020-12-14 04:15

The quadratic/cubic bézier curve code I find via google mostly works by subdividing the line into a series of points and connects them with straight lines. The rasterization

4条回答
  •  暖寄归人
    2020-12-14 04:40

    First of all, I'd like to say that the fastest and the most reliable way to render bezier curves is to approximate them by polyline via adaptive subdivision, then render the polyline. Approach by @markE with drawing many points sampled on the curve is rather fast, but it can skip pixels. Here I describe another approach, which is closest to line rasterization (though it is slow and hard to implement robustly).

    I'll treat usually curve parameter as time. Here is the pseudocode:

    1. Put your cursor at the first control point, find the surrounding pixel.
    2. For each side of the pixel (four total), check when your bezier curves intersects its line by solving quadratic equations.
    3. Among all the calculated side intersection times, choose the one which will happen strictly in future, but as early as possible.
    4. Move to neighboring pixel depending on which side was best.
    5. Set current time to time of that best side intersection.
    6. Repeat from step 2.

    This algorithm works until time parameter exceeds one. Also note that it has severe issues with curves exactly touching a side of a pixel. I suppose it is solvable with a special check.

    Here is the main code:

    double WhenEquals(double p0, double p1, double p2, double val, double minp) {
        //p0 * (1-t)^2 + p1 * 2t(1 - t) + p2 * t^2 = val
        double qa = p0 + p2 - 2 * p1;
        double qb = p1 - p0;
        double qc = p0 - val;
        assert(fabs(qa) > EPS); //singular case must be handled separately
        double qd = qb * qb - qa * qc;
        if (qd < -EPS)
            return INF;
        qd = sqrt(max(qd, 0.0));
        double t1 = (-qb - qd) / qa;
        double t2 = (-qb + qd) / qa;
        if (t2 < t1) swap(t1, t2);
        if (t1 > minp + EPS)
            return t1;
        else if (t2 > minp + EPS)
            return t2;
        return INF;
    }
    
    void DrawCurve(const Bezier &curve) {
        int cell[2];
        for (int c = 0; c < 2; c++)
            cell[c] = int(floor(curve.pts[0].a[c]));
        DrawPixel(cell[0], cell[1]);
        double param = 0.0;
        while (1) {
            int bc = -1, bs = -1;
            double bestTime = 1.0;
            for (int c = 0; c < 2; c++)
                for (int s = 0; s < 2; s++) {
                    double crit = WhenEquals(
                        curve.pts[0].a[c],
                        curve.pts[1].a[c],
                        curve.pts[2].a[c],
                        cell[c] + s, param
                    );
                    if (crit < bestTime) {
                        bestTime = crit;
                        bc = c, bs = s;
                    }
                }
            if (bc < 0)
                break;
            param = bestTime;
            cell[bc] += (2*bs - 1);
            DrawPixel(cell[0], cell[1]);
        }
    }
    

    Full code is available here. It uses loadbmp.h, here it is.

提交回复
热议问题