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

Time:12-11

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.)

CodePudding user response:

Your code works. A mistake found, 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;
}

  • Related