Search code examples
javascripthtmlcanvas

html canvas setting 2 image layers to 2 different colors not working


I have 3 images: an outline, a white base, and a pattern that is also white. What I am trying to do is stack these where base is at the bottom and a color is assigned to it, pattern is stacked on top of that with a different color, and the outline is on top of that. The problem I am having is when I set the color of pattern it is changing over the base color as well instead of just where the pattern color is.

      context.drawImage(baseImage, 0, 0, canvasWidth, canvasHeight);

      context.globalCompositeOperation = 'source-in';
      context.fillStyle = "#FFC3E1";
      context.fillRect(0, 0, canvasWidth, canvasHeight);
      context.globalCompositeOperation = 'source-over';

      context.drawImage(patternImage, 0, 0, canvasWidth, canvasHeight);
      context.globalCompositeOperation = 'source-in';
      context.fillStyle = "#70E9EE";
      context.fillRect(0, 0, canvasWidth, canvasHeight);
      context.globalCompositeOperation = 'destination-over';

      context.drawImage(outlineImage, 0, 0, canvasWidth, canvasHeight);
    });

I have tried setting context.globalCompositeOperation to different things bun none that I have tried have worked. How do I get it so the pattern color does not override the base color?


Solution

  • When drawing with canvas, every step needs to be thought through a bit more because it feels unintuitive, but makes sense. You should probably start with the pattern you want to color differently, draw that, color it in, then fill the remaining pixels with you white base, and then draw your outline on top of it. Here's a little example:

    const canvas = document.createElement( 'canvas' );
    const ctx = canvas.getContext( '2d' );
    
    canvas.width =
    canvas.height = 400;
    
    ctx.fillStyle = ctx.createPattern( aPattern(), "repeat" );
    ctx.fillRect( 0, 0, canvas.width, canvas.height );
    
    ctx.globalCompositeOperation = 'source-in';
    ctx.fillStyle = 'red';
    ctx.fillRect( 0, 0, canvas.width, canvas.height );
    
    ctx.globalCompositeOperation = 'destination-over';
    ctx.fillStyle = 'gray';
    ctx.fillRect( 0, 0, canvas.width, canvas.height );
    
    ctx.globalCompositeOperation = 'source-over';
    ctx.strokeStyle = 'white';
    ctx.lineWidth = 10;
    ctx.strokeRect( 20, 20, canvas.width - 40, canvas.height - 40 );
    
    document.body.appendChild( canvas );
    
    
    function aPattern(){
        
      const canvas = document.createElement( 'canvas' );
      const ctx = canvas.getContext( '2d' );
      
      canvas.width = canvas.height = 100;
        
      ctx.lineWidth = 5;
      ctx.strokeRect( 10, 10, 80, 80 );
      
      return canvas;
      
    }

    Here's a little snippet that runs a loop so you can see and visualise the steps a bit easier:

    const canvas = document.getElementById( 'canvas' );
    const ctx = canvas.getContext( '2d' );
    const operation = document.getElementById( 'operation' );
    const speed = document.getElementById( 'speed' );
    
    let d = speed.valueAsNumber;
    
    speed.addEventListener( 'input', event => { d = speed.valueAsNumber; });
    
    async function delay( d, message ){
    
      operation.textContent = message || '';
      
      return new Promise(r => setTimeout(r, d));
      
    }
    async function loop(){
    
      canvas.width =
      canvas.height = 400;
      
      ctx.save();
      
      await delay(d, 'Resetting canvas to empty');
    
      ctx.fillStyle = ctx.createPattern( aPattern(), "repeat" );
      ctx.fillRect( 0, 0, canvas.width, canvas.height );
    
      await delay(d, 'Add your incorrectly colored pattern');
      
      ctx.globalCompositeOperation = 'source-in';
      ctx.fillStyle = 'red';
      ctx.fillRect( 0, 0, canvas.width, canvas.height );
    
      await delay(d, 'Color the pattern in based on filled pixels');
      
      ctx.globalCompositeOperation = 'destination-over';
      ctx.fillStyle = 'gray';
      ctx.fillRect( 0, 0, canvas.width, canvas.height );
    
      await delay(d, 'Draw the next image/color behind the pattern');
      
      ctx.globalCompositeOperation = 'source-over';
      ctx.strokeStyle = 'white';
      ctx.lineWidth = 10;
      ctx.strokeRect( 20, 20, canvas.width - 40, canvas.height - 40 );
    
      await delay(d, 'Draw your outline on top of the pattern and background');
      
      ctx.restore();
      
      return loop();
    
    };
    
    loop();
    
    function aPattern(){
        
      const canvas = document.createElement( 'canvas' );
      const ctx = canvas.getContext( '2d' );
      
      canvas.width = canvas.height = 100;
        
      ctx.lineWidth = 5;
      ctx.strokeRect( 10, 10, 80, 80 );
      
      return canvas;
      
    }
    <div id="operation"></div>
    <br /><canvas id="canvas" width="400" height="400"></canvas>
    <br /><input type="range" min="100" max="10000" value="1000" id="speed" />