canvas js fill an arc with image

I am trying to fill a circle (arc) with an image.

Here is my code :

draw() {
ctx.save();
let boulePat = new Image();
switch(this.couleur) {
  case "red":
    boulePat.src = "images/red.png";
    break;
  case "green":
    boulePat.src = "images/green.png";
    break;
  case "orange":
    boulePat.src = "images/orange.png";
    break;
  case "yellow":
    boulePat.src = "images/yellow.png";
    break; 
  case "purple":
    boulePat.src = "images/purple.png";
    break;
 }
  var pattern = ctx.createPattern(boulePat, "repeat");
  ctx.beginPath();
  ctx.arc(this.x, this.y, 15, 0, 2 * Math.PI);
  ctx.fillStyle = pattern;
  ctx.fill();
  ctx.restore();
}

With this, I have empty or black circles …

Could you help me, please ?
Thank you very much.

20 thoughts on “canvas js fill an arc with image”

  1. The code below takes an image URL, the radius of the arc, and the angle/size of the arc, and returns a canvas that has that image clipped to the given arc, and "contained" within the arc such that the image fills the arc without changing the image’s aspect ratio:

      async function getArcClippedCanvas(imageUrl, radius, arcSizeDeg) {   
        let arcSizeRad = (arcSizeDeg/360)*2*Math.PI;
        
        // derive required width and height of canvas from radius and arc size
        let width;
        if(arcSizeDeg >= 180) {
          width = radius*2;
        } else {
          width = radius*Math.sin(arcSizeRad/2)*2;
        }
        
        let height;
        if(arcSizeDeg <= 180) {
          height = radius;
        } else {
          height = radius + radius*Math.sin( (arcSizeRad-Math.PI)/2 );
        }
        
        let arcCenterX = width/2;
        let arcCenterY = radius; // remember, y axis starts from top of canvas
         
        let canvas = document.createElement("canvas");
        let ctx = canvas.getContext("2d");
        
        canvas.width = width;
        canvas.height = height;
        
        let img = new Image();
        await new Promise(resolve => {
          img.onload = resolve;
          img.src = imageUrl;
        });
        
        let centerAngle = -Math.PI/2;
        
        ctx.beginPath();
        ctx.moveTo(arcCenterX, arcCenterY); 
        ctx.arc(arcCenterX, arcCenterY, radius, centerAngle - (arcSizeDeg/2)*2*Math.PI/360, centerAngle + (arcSizeDeg/2)*2*Math.PI/360);
        ctx.clip();
        
        // we want to "cover" the canvas with the image without changing the image's aspect ratio
        drawImageToCanvasContained(ctx, img, 0, 0, canvas.width, canvas.height);
        
        return canvas;
      }
    
    
      function drawImageToCanvasContained(ctx, img, x, y, w, h, offsetX, offsetY) {
        // By Ken Fyrstenberg Nilsen: https://stackoverflow.com/a/21961894/11950764
        if(arguments.length === 2) {
          x = y = 0;
          w = ctx.canvas.width;
          h = ctx.canvas.height;
        }
    
        // default offset is center
        offsetX = typeof offsetX === "number" ? offsetX : 0.5;
        offsetY = typeof offsetY === "number" ? offsetY : 0.5;
    
        // keep bounds [0.0, 1.0]
        if(offsetX < 0) offsetX = 0;
        if(offsetY < 0) offsetY = 0;
        if(offsetX > 1) offsetX = 1;
        if(offsetY > 1) offsetY = 1;
    
        let iw = img.width;
        let ih = img.height;
        let r = Math.min(w / iw, h / ih);
        let nw = iw * r;   // new prop. width
        let nh = ih * r;   // new prop. height
        let cx, cy, cw, ch, ar = 1;
    
        // decide which gap to fill    
        if(nw < w) ar = w / nw;                             
        if(Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh;  // updated
        nw *= ar;
        nh *= ar;
    
        // calc source rectangle
        cw = iw / (nw / w);
        ch = ih / (nh / h);
    
        cx = (iw - cw) * offsetX;
        cy = (ih - ch) * offsetY;
    
        // make sure source rectangle is valid
        if(cx < 0) cx = 0;
        if(cy < 0) cy = 0;
        if(cw > iw) cw = iw;
        if(ch > ih) ch = ih;
    
        // fill image in dest. rectangle
        ctx.drawImage(img, cx, cy, cw, ch,  x, y, w, h);
      }
      
    
    Reply
  2. You have to wait for the image to load before you can use it in createPattern, like so:

    See this stack answer

    Keep in mind, the image will start from 0,0 coordinates in relation to the canvas. You will need to factor this into any offsets (using ctx.transform(xOffset, yOffset)) if necessary.

    var canvas = document.getElementById("myCanvasNoTranslate");
    var canvas2 = document.getElementById("myCanvasWithTranslate");
    
    function drawCanvas(_canvas) {
      var context = _canvas.getContext("2d");
      draw(context);
    }
    
    function draw(ctx) {
      ctx.save();
      ctx.strokeSyle = "rgb(0, 0, 0)";
      ctx.lineWidth = 3;
      ctx.strokeRect(0, 0, 400, 200);
      
      let boulePat = new Image();
      boulePat.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/16777216colors.png/100px-16777216colors.png";
      boulePat.onload = function () {
        var pattern = ctx.createPattern(boulePat, "repeat");
        ctx.fillStyle = pattern;
        ctx.beginPath();
        ctx.arc(100, 100, 50, 0, 2 * Math.PI);
        // translate canvas to offset where the image coordinates are for .fill()
        if (ctx.canvas === canvas2) {
          ctx.translate(50, 50);  
        }
        ctx.fill();
        // restore ctx back to before the translate()
        ctx.restore();
      }
    }
    
    drawCanvas(canvas);
    drawCanvas(canvas2);
    With ctx.translate() for background fill<br/>
    <canvas id="myCanvasWithTranslate" height="200px" width="400px"></canvas>
    <br/><br/>
    Without ctx.translate() for background fill<br/>
    <canvas id="myCanvasNoTranslate" height="200px" width="400px"></canvas>
    Reply

Leave a Comment