Search code examples
javascriptcanvasgraphicshtml5-canvasscaling

Scaling background pattern fill in html5 canvas tag


I have a canvas tag displaying a set of paths and rectangles.

I zoom into these paths and rectangles by using the ctx.scale(2,2) function and then redraw them with the new scale so they are sharp.

I would like to have a simple textured background for some of the rectangles, i.e. one pixel black one white, however when I apply the texture as a fill to the scaled rectangle, canvas scales the background pattern as well. I would like the background pattern to remain at the original scale of one pixel black, one pixel white. But cannot seem to figure out how to do this with it looking blurry. I have a example here : http://jsfiddle.net/4UxWg/

var patternCanvas = document.createElement('canvas');
var pattern_ctx = patternCanvas.getContext("2d");
patternCanvas.width = 1;
patternCanvas.height = 2;

pattern_ctx.fillRect(0,0,1,1);


var mainCanvas = document.createElement('canvas');
var ctx = mainCanvas.getContext("2d");
mainCanvas.height = 500;
mainCanvas.width = 400;

document.getElementsByTagName("body")[0].appendChild(mainCanvas);

//scale function - remove this line to see how the pattern should look like
ctx.scale(5,5); 

var pattern = ctx.createPattern(patternCanvas, "repeat");
ctx.fillStyle= pattern;

ctx.fillRect(2,2,40,40); //fillRect looks wierd and pattern is no longer 1px by 1px

thanks for any help!


Solution

  • Update I read background as the global background behind all the shapes. But it seem to be the fill of the shapes if I now understand it correctly - in that case you need to do things slightly different:

    To keep filled pattern unaffected by scaled dimensions of the shapes you will need to manually scale the shapes -

    Either:

    var scale = 5;
    
    ctx.fillRect(x * scale, y * scale, w * scale, h * scale);
    

    or use a wrapper function:

    function fillRectS(x, y, w, h) {
        ctx.fillRect(x * scale, y * scale, w * scale, h * scale);
    }
    
    fillRectS(x, y, w, h);
    

    For stroke you just do the same:

    function lineWidthS(w) {
        ctx.lineWidth = w * scale;
    }
    

    and so forth (lines, moveTo, arc). This will allow you to scale all shapes but keep pattern fill at 1:1 ratio. The overhead will be minimal.

    Old answer -

    There are at least two ways around this:

    1) You can reset the translation when you fill the pattern and then re-apply transformations right after.

    /// reset
    ctx.transform(1, 0, 0, 1, 0, 0);
    
    ctx.fillStyle = pattern;
    ctx.fillRect(x, y, w, h);
    
    ctx.scale(sx, sy);
    

    2) Layer the canvases so that the background is drawn separately on one canvas and use the top canvas to draw in anything else that needs to be drawn in scale.

    Different scales and transformations used simultaneously is one good case scenario to use layered canvas.

    HTML:

    <div id="container">
        <canvas id="bg" ... ></canvas>
        <canvas id="main" ... ></canvas>
    </div>
    

    CSS:

    #container {
        position:relative;
        }
    #container > canvas {
        position:absolute;
        left:0;
        top:0;
        }
    

    Then get context for each and draw in as needed (pseudo-ish):

    var bgCtx = bg.getContext('2d');
    var ctx = main.getContext('2d');
    
    ... other setup code here ...
    
    /// will only affect foreground canvas
    ctx.scale(5, 5);
    
    /// will only fill background canvas
    bgCtx.fillStyle = pattern;
    bgCtx.fillRect(x, y, w, h);
    

    Now these won't affect each other.