Search code examples
calgorithmcurvebezier

How to draw a quadratic Bézier curve on a linear framebuffer


As the title says, I want to draw a quadratic Bézier curve on a linear framebuffer (aka plotting pixels).

I already have a function that's drawing a cubic Bézier curve according to https://www.joshondesign.com/2018/07/11/bezier-curves (I translated the javascript into C like this https://gist.github.com/cheyao/a736a58c4cf683eabea2aa2a87718ef1#file-cubic-c) which works fine.

And now I tried to convert it into drawing a quadratic Bézier curve (like this https://gist.github.com/cheyao/a736a58c4cf683eabea2aa2a87718ef1#file-flatness-c), which is looking not-fine for me (too not curvish).

I also tried to brutal force it like this

void quadratic_bezier_curve(const Vector2 p[3], const color_t color) {
    for (double t = 0; t < 1; t += 0.001) {
        putPixel((uint32_t) ((1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x),
                 (uint32_t) ((1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y), color);
    }
}

Which makes the like look bold.

So my question comes: Does anyone know some better algorithms to draw a (quadratic) Bézier curves? (I need it for font rendering.)


Solution

  • Your code works. Found a mistake, that for flatness we need to check absolute values. And there was a typo that p[1].y was used twice instead of once. So it would be like:

    static int32_t quadratic_flatness(const Vector2 p[3]) {
        return abs((int32_t) p[1].x - (((int32_t) p[0].x + (int32_t) p[2].x) / 2))
             + abs((int32_t) p[1].y - (((int32_t) p[0].y + (int32_t) p[2].y) / 2));
    }
    

    Live example in JS (click 'Run code snippet' button and start clicking with mouse left button on the canvas):

    var canvas = document.createElement('canvas');
    document.body.appendChild(canvas);
    document.body.style.margin = 0;
    canvas.style.position = 'fixed';
    
    // get canvas 2D context and set him correct size
    var ctx = canvas.getContext('2d');
    resize();
    
    let points = [];
    
    window.addEventListener('resize', resize);
    document.addEventListener('mousedown', setPosition);
    
    function quadratic_split_curve(p) { // out[2][3]
        const p12 = midpoint(p[0], p[1]);
        const p23 = midpoint(p[1], p[2]);
        const p123 = midpoint(p12, p23);
      return [
            [p[0], p12, p123],
            [p123, p23, p[2]]
      ];
    }
    
    function quadratic_flatness(p) {
        const result = Math.abs(p[1].x - (Math.floor( p[0].x + p[2].x) / 2)) +  Math.abs(p[1].y - (Math.floor( p[0].y +  p[2].y) / 2));
      return result;
    }
    
    function quadratic_bezier_curve(p, color) {
        if (quadratic_flatness(p) < 2) {
            line(p[0], p[2], color);
            return;
        } else {
            const split = quadratic_split_curve(p);
            quadratic_bezier_curve(split[0], color);
            quadratic_bezier_curve(split[1], color);
        }
    }
    
    function midpoint(a, b) {
        return {
        x: (a.x + b.x)/2,
        y: (a.y + b.y)/2
      }
    }
    
    // new position from mouse event
    function setPosition(e) {
     if(points.length >= 3) {
            points = [];
      }
      points.push({ x: e.clientX, y: e.clientY });
      draw();
    }
    
    function resize() {
      ctx.canvas.width = window.innerWidth;
      ctx.canvas.height = window.innerHeight;
    }
    
    function clear() {
        ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
    }
    function dot(point) {
        ctx.fillRect(point.x-1,point.y-1,3,3);
    }
    function line(a, b, color) {
        ctx.strokeStyle = color;
        ctx.beginPath(); 
        ctx.moveTo(a.x, a.y);
        ctx.lineTo(b.x, b.y); 
        ctx.stroke();
    }
    function draw() {
        clear();
      for(const point of points) {
        dot(point);
      }
      
      if(points.length == 3) {
        ctx.lineWidth = 1;
        quadratic_bezier_curve(points, '#c0392b');
      }
      
      if(points.length > 1) {
      
        ctx.lineWidth = 2;
        ctx.strokeStyle = '#3039cb77';
        ctx.beginPath(); 
        ctx.moveTo(points[0].x, points[0].y);
        
        for(i=1; i<points.length; i++) {
    
          ctx.lineTo(points[i].x, points[i].y); 
        }
        ctx.stroke();
      }
      
      return;
    }