Georgy Liparteliani
Georgy Liparteliani

Reputation: 403

Use pattern to cover path in canvas

I have such function:

function fillSlide(slideId){
        var context_background = new Image();
        context_background.src = './images/'+slideId+'.png'; 
        context_background.onload = function(){
            var canvas = document.getElementById(slideId);

            window.addEventListener('resize', resizeCanvas, false);

            if(canvas.getContext){
                var context = canvas.getContext('2d');
                var pattern = context.createPattern(this,'no-repeat');

                function resizeCanvas() {
                    var width = window.innerWidth;
                    var height = document.getElementsByClassName('slidecont')[0].offsetHeight;
                    height += 50;
                    canvas.width = width;
                    canvas.height = height;

                    drawStuff(context,width,pattern);
                }
                resizeCanvas();

                function drawStuff(ctx,w,p) {
                    var l = w/2 - 120;
                    var r = w/2 + 120;

                    context.fillStyle = p;
                    ctx.save();
                    ctx.beginPath();
                    ctx.moveTo(0,50);
                    ctx.lineTo(0,1924.06925);
                    ctx.lineTo(w,1924.06925);
                    ctx.lineTo(w,50);
                    ctx.lineTo(r,50);
                    ctx.bezierCurveTo((r-35),50,(r-70),0,(r-120),0);
                    ctx.bezierCurveTo((l+70),0,(l+49),50,l,50);
                    ctx.lineTo(0,50);
                    ctx.closePath();
                    ctx.fill();
                    ctx.restore();
                }

            }
        };
    }

As you can see I am using an Image() element to create pattern and fill the context with it. I want to use this pattern as a background. But there is no background-size style in canvases.

As I see, I need to cut the pattern element before filling the canvas. How can I manage that? Thx.

UPD: JSFIDDLE

Upvotes: 0

Views: 1396

Answers (1)

Sławek Dróżdż
Sławek Dróżdż

Reputation: 29

Maybe my response is not worth of adding answer but I can't add comment yet.

I think this might help you.

Edit

You doesn't need fillStyle pattern to fill shape with image. Another approach is:

  1. draw the path
  2. call clip method on context object (this will crop every drawn object to match the shape specified by path)
  3. use drawImage method instead of pattern so you can set appropriate size.

Look at my jsfiddle snippet

var canvas = document.getElementById('slide');
var ctx = canvas.getContext('2d');
var img = new Image();

function draw() {
    var w = canvas.width;
    var h = canvas.height;
    var l = w/2 - 120;
    var r = w/2 + 120; 
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(0,50);
    ctx.lineTo(0,h);
    ctx.lineTo(w,h);
    ctx.lineTo(w,50);
    ctx.lineTo(r,50);
    ctx.bezierCurveTo((r-35),50,(r-70),0,(r-120),0);
    ctx.bezierCurveTo((l+70),0,(l+49),50,l,50);
    ctx.lineTo(0,50);
    ctx.closePath();
    ctx.clip();
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    ctx.restore();
}

function fitCanvasSize() {
    canvas.width = document.body.clientWidth;
    canvas.height = document.body.clientHeight;
}

window.addEventListener('resize', function () {
    fitCanvasSize();
    draw();
});

img.onload = function () {
    draw();
}
fitCanvasSize();
img.src = 'https://placeholdit.imgix.net/~text?txtsize=28&txt=300%C3%97300&w=300&h=300';

Edit 2, keeping image aspect ratio

To keep aspect ration of image image can be cutted and centered like here. To achieve this, we need additional variables:

var sx, sy, sWidth, sHeight;
var imgAspectRatio = img.width / img.height;
var areaAspectRatio = w / h;

Purpose of sx, sx, sWidth, sHeight is explained in MDN

sx: The X coordinate of the top left corner of the sub-rectangle of the source image to draw into the destination context.

sy: The Y coordinate of the top left corner of the sub-rectangle of the source image to draw into the destination context.

sWidth: The width of the sub-rectangle of the source image to draw into the destination context. If not specified, the entire rectangle from the coordinates specified by sx and sy to the bottom-right corner of the image is used.

sHeight: The height of the sub-rectangle of the source image to draw into the destination context.

Below are default values which remain the same if imgAspectRatio equals areaAspectRatio:

sx = 0;
sy = 0;
sWidth = img.width;
sHeight = img.height;

Otherwise:

if (imgAspectRatio > areaAspectRatio) {
    // image is centered horizontally
    sWidth = (areaAspectRatio / imgAspectRatio) * img.width;
    sx = (img.width - sWidth ) / 2;
} else if (imgAspectRatio < areaAspectRatio) {
    // image is centered vertically
    sHeight = (imgAspectRatio / areaAspectRatio) * img.height;
    sy = (img.height - sHeight) / 2;
}

Next draw image:

ctx.drawImage(img, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height);

Final code:

var canvas = document.getElementById('slide');
var ctx = canvas.getContext('2d');
var img = new Image();

function draw() {
    var w = canvas.width;
    var h = canvas.height;
    var sx, sy, sWidth, sHeight;
    var imgAspectRatio = img.width / img.height;
    var areaAspectRatio = w / h;

    // below values remains unchanged if 
    // image aspect ratio === area aspect ratio
    sx = 0;
    sy = 0;
    sWidth = img.width;
    sHeight = img.height;

    if (imgAspectRatio > areaAspectRatio) {
        // image is centered horizontally
        sWidth = (areaAspectRatio / imgAspectRatio) * img.width;
        sx = (img.width - sWidth ) / 2;
    } else if (imgAspectRatio < areaAspectRatio) {
        // image is centered vertically
        sHeight = (imgAspectRatio / areaAspectRatio) * img.height;
        sy = (img.height - sHeight) / 2;
    }
    var l = w/2 - 120;
    var r = w/2 + 120; 
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(0,50);
    ctx.lineTo(0,h);
    ctx.lineTo(w,h);
    ctx.lineTo(w,50);
    ctx.lineTo(r,50);
    ctx.bezierCurveTo((r-35),50,(r-70),0,(r-120),0);
    ctx.bezierCurveTo((l+70),0,(l+49),50,l,50);
    ctx.lineTo(0,50);
    ctx.closePath();
    ctx.clip();
    ctx.drawImage(img, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height);
    ctx.restore();
}

function fitCanvasSize() {
    canvas.width = document.body.clientWidth;
    canvas.height = document.body.clientHeight;
}

window.addEventListener('resize', function () {
    fitCanvasSize();
    draw();
});

img.onload = function () {
    draw();
}
fitCanvasSize();
img.src = 'http://upload.wikimedia.org/wikipedia/commons/3/3f/Brown-bear-in-spring.jpg';

Easier approach can be keeping canvas aspect ratio instead of cutting image.

This example works because canvas is bounding box of clipping area. If clipping area will be smaller comparing to canvas then:

the image will "float" around the clipped area (see markE comment)

I can write the solution for this problem if somebody will be asking for that.

Upvotes: 1

Related Questions