1

I need to draw and fill a not anti-aliased circle for a basic drawing app in HTML5Canvas, because fill bucket tools algorithms dont fill anti-aliased shapes border nicely.

I took the javascript algorithm of this page https://en.wikipedia.org/wiki/Midpoint_circle_algorithm and implement it to draw filled circle, but its very slow.

canvas = document.getElementById("canvas");
const CHANNELS_PER_PIXEL = 4; //rgba

function drawCircle (x0, y0, radius, canvas) {
var x = radius-1;
var y = 0;
var dx = 1;
var dy = 1;
var decisionOver2 = dx - (radius << 1);   // Decision criterion divided by 2 evaluated at x=r, y=0
var imageWidth = canvas.width;
var imageHeight = canvas.height;
var context = canvas.getContext('2d');
var imageData = context.getImageData(0, 0, imageWidth, imageHeight);
var pixelData = imageData.data;
var makePixelIndexer = function (width) {
    return function (i, j) {
        var index = CHANNELS_PER_PIXEL * (j * width + i);
        //index points to the Red channel of pixel 
        //at column i and row j calculated from top left
        return index;
    };
};
var pixelIndexer = makePixelIndexer(imageWidth);
var drawPixel = function (x, y) {
    var idx = pixelIndexer(x,y);
    pixelData[idx] = 152;   //red
    pixelData[idx + 1] = 152; //green
    pixelData[idx + 2] = 152;//blue
    pixelData[idx + 3] = 255;//alpha
};

while (x >= y) {

    if(x0 + x>=0){drawPixel(x0 + x, y0 + y);}
    if(x0 + y>=0){drawPixel(x0 + y, y0 + x);}
    if(x0 - x>=0){drawPixel(x0 - x, y0 + y);} 
    if(x0 - y>=0){drawPixel(x0 - y, y0 + x);} 
    if(x0 - x>=0){drawPixel(x0 - x, y0 - y);}
    if(x0 - y>=0){drawPixel(x0 - y, y0 - x);}
    if(x0 + x>=0){drawPixel(x0 + x, y0 - y);}
    if(x0 + y>=0){drawPixel(x0 + y, y0 - x);}

    //fill circle code
    var x1=x0-x;
    var x2=x0+x;
    var xx=x2-x1;

        for(i=x2-x1;i>0; i--){
            if((x1+(xx-i))>=0){
            drawPixel(x1+(xx-i),y0+y);
            }
        }

    var x1=x0-y;
    var x2=x0+y;
    var xx=x2-x1;

        for(i=x2-x1;i>0; i--){
            if((x1+(xx-i))>=0){
            drawPixel(x1+(xx-i),y0+x);
            }
        }

    var x1=x0-x;
    var x2=x0+x;
    var xx=x2-x1;

        for(i=x2-x1;i>0; i--){
            if((x1+(xx-i))>=0){
            drawPixel(x1+(xx-i),y0-y);
            }
        }

    var x1=x0-y;
    var x2=x0+y;
    var xx=x2-x1;

        for(i=x2-x1;i>0; i--){
            if((x1+(xx-i))>=0){
            drawPixel(x1+(xx-i),y0-x);
            }
        }
    //fill end

    if (decisionOver2 <= 0) 
    {
        y++;
        decisionOver2 += dy; // Change in decision criterion for y -> y+1
        dy += 2;
    } 
    if (decisionOver2 > 0)
    {
        x--;
        dx += 2;
        decisionOver2 += (-radius << 1) + dx; // Change for y -> y+1, x -> x-1
    }
}

context.putImageData(imageData, 0, 0);
}

Also,

context.translate(0.5, 0.5);

and

context.imageSmoothingEnabled = !1;

dont work for a circle.

Do you have better functions or do you know how to compress and concatenate this circle algorithm ?

Thanks

1 Answers1

4

I made this modified version of the Breseham circle algorithm to fill "aliased" circles a while back for a "retro" project.

The modification is taking the values from the 8 slices and converts them to 4 lines. We can use rect() to create a line but have to convert the absolute (x2,y2) coordinate to width and height instead.

The method simply add rect's to the path which is pretty fast and you don't have to go via the slow getImageData()/putImageData() (and doesn't get inflicted by CORS issues). And at the end one single fill operation is invoked. This means you can also use this directly on the canvas without having to be worry about existing content in most cases.

It's important that the translate and the given values are integer values, and that radius > 0.

To force integer values simply shift the value 0:

xc = xc|0;  // you can add these to the function below
yc = yc|0;
r  = r|0;

(If you should want to make an outline ("stroked") version you would have to use all 8 slices' positions and change width for rect() to 1.)

Demo

snapshot 4x

var ctx = c.getContext("2d");
ctx.fillStyle = "#09f";
aliasedCircle(ctx, 200, 200, 180);
ctx.fill();
  
function aliasedCircle(ctx, xc, yc, r) {  // NOTE: for fill only!
  var x = r, y = 0, cd = 0;

  // middle line
  ctx.rect(xc - x, yc, r<<1, 1);

  while (x > y) {
    cd -= (--x) - (++y);
    if (cd < 0) cd += x++;
    ctx.rect(xc - y, yc - x, y<<1, 1);    // upper 1/4
    ctx.rect(xc - x, yc - y, x<<1, 1);    // upper 2/4
    ctx.rect(xc - x, yc + y, x<<1, 1);    // lower 3/4
    ctx.rect(xc - y, yc + x, y<<1, 1);    // lower 4/4
  }
}
<canvas id=c width=400 height=400></canvas>

Zoomed-in demo:

var ctx = c.getContext("2d");
ctx.scale(4,4);
ctx.fillStyle = "#09f";
aliasedCircle(ctx, 50, 50, 45);
ctx.fill();

ctx.font = "6px sans-serif";
ctx.fillText("4x", 2, 8);

function aliasedCircle(ctx, xc, yc, r) {
  var x = r, y = 0, cd = 0;

  // middle line
  ctx.rect(xc - x, yc, r<<1, 1);

  while (x > y) {
    cd -= (--x) - (++y);
    if (cd < 0) cd += x++;
    ctx.rect(xc - y, yc - x, y<<1, 1);  // upper 1/4
    ctx.rect(xc - x, yc - y, x<<1, 1);  // upper 2/4
    ctx.rect(xc - x, yc + y, x<<1, 1);  // lower 3/4
    ctx.rect(xc - y, yc + x, y<<1, 1);  // lower 4/4
  }
}
<canvas id=c width=400 height=400></canvas>
Community
  • 1
  • 1
  • 1
    Thank you very much, really fast! :D – garcia venture Aug 18 '17 at 00:27
  • Added a small optimization for the width calculation. –  Aug 18 '17 at 04:02
  • I have done a drawing function with your nice circles here: https://jsfiddle.net/eu34c8sy/6/ but it becomes jerky on fast drawing with huge circles. I would be very interested in your advices. I can make an other question about it if you want :) – garcia venture Aug 18 '17 at 04:15
  • 1
    @garciaventure yeah, drawing a tight line using a large circle radius can possibly put a lot of stress on the poor canvas. There is a better technique for this, but will suit better in a new question :) –  Aug 18 '17 at 04:25
  • https://stackoverflow.com/questions/45749018/how-to-make-a-fast-not-anti-aliasing-html5canvas-basic-drawing-function :) – garcia venture Aug 18 '17 at 05:20