4

Guess the title of the post may need editing, but for now I don't know where the problems are. I have read pages and answers to similar questions, here and elsewhere. One Stack Overflow answer is especially close, but I don't understand it.

I want a function, to draw polygons on canvas at desired coordinates and to fill them with some background image loaded from a file (large enough that no tiling is needed). Triangles would be fine for a test. Apparently I should use drawImage and clip, and to give the polygon a border, I can resuse the same path for the clip and the stroke. Also apparently I should keep the order of

- define path
- save
- clip
- drawImage
- restore
- stroke.

Also read somewhere that it is enough to load the image once. (If uou want me to quote sources for all these assumptions, I will look for where I saw them. Most of them on Stack Overflow)

The HTML is an otherwise empty

<body onload = "main ();"></body>

First approach, pretending that the browser will wait for the picture to load:

var ctx, img;
var image_path = 'bg.jpg';

function main () {

    var CANVAS_SIZE = 600;
    var view_field_cnv = document.createElement ('canvas');
    view_field_cnv.width  = CANVAS_SIZE;
    view_field_cnv.height = CANVAS_SIZE;
    view_field_cnv.style.border = "1px solid";
    document.body.appendChild (view_field_cnv);
    ctx = view_field_cnv.getContext ('2d');

    img = document.createElement ('img');
    img.src = image_path;

    place_triangle (0, 0);
    place_triangle (300, 300);
    place_triangle (500, 500);
    place_triangle (0, 0);

}

function place_triangle (x, y) {

    console.log (x, y);

    ctx.beginPath ();
    ctx.moveTo (x + 10, y);
    ctx.lineTo (x + 110, y);
    ctx.lineTo (x + 60, y + 40);
    ctx.closePath ();

    img = document.createElement ('img');
    img.src = image_path;

    ctx.save ();
    ctx.clip ();
    ctx.drawImage (img, x, y);
    ctx.restore ();
    ctx.stroke ();


}

That draws all three triangles but no clipped images.

Second try, with drawImage inside image.onload:

var ctx;
var image_path = 'bg.jpg';

function main () {

    var CANVAS_SIZE = 600;
    var view_field_cnv = document.createElement ('canvas');
    view_field_cnv.width  = CANVAS_SIZE;
    view_field_cnv.height = CANVAS_SIZE;
    view_field_cnv.style.border = "1px solid";
    document.body.appendChild (view_field_cnv);
    ctx = view_field_cnv.getContext ('2d');

    place_triangle (0, 0);
    place_triangle (300, 300);
    place_triangle (500, 500);
    place_triangle (0, 0);

}

function place_triangle (x, y) {

    console.log (x, y);

    var img;

    ctx.beginPath ();
    ctx.moveTo (x + 10, y);
    ctx.lineTo (x + 110, y);
    ctx.lineTo (x + 60, y + 40);
    ctx.closePath ();

    img = document.createElement ('img');
    img.src = image_path;
    img.onload = function () {

        ctx.save ();
        ctx.clip ();
        ctx.drawImage (img, x, y);
        ctx.restore ();
        ctx.stroke ();
    }

}

This one does draw the clipped image, but only one triangle, the last one. Just commenting out save and restore doesn't help.

So, I don't understand loading images, saving, restoring and probably a million other things. Where be the bugs?

markE
  • 102,905
  • 11
  • 164
  • 176
user3334085
  • 53
  • 1
  • 4

2 Answers2

7

I see you already understand the basics of clipping:

  • save context, define path, clip, drawImage, restore context.

  • you can stroke after restore if you want the stroke to slightly overlap the clipped image.

  • you can stroke before clipping if you don't want the stroke to overlap the clipped image.

enter image description here

Here's example code and a Demo: http://jsfiddle.net/m1erickson/p0fup425/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    // image loader
    // put the paths to your images in imageURLs[]
    var imageURLs=[];  
    // push all your image urls!
    imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/norwayFlag.jpg");
    imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/swedishFlag.jpg");

    // the loaded images will be placed in images[]
    var imgs=[];

    var imagesOK=0;
    loadAllImages(start);

    function loadAllImages(callback){
        for (var i=0; i<imageURLs.length; i++) {
            var img = new Image();
            imgs.push(img);
            img.onload = function(){ 
                imagesOK++; 
                if (imagesOK>=imageURLs.length ) {
                    callback();
                }
            };
            img.onerror=function(){alert("image load failed");} 
            img.crossOrigin="anonymous";
            img.src = imageURLs[i];
        }      
    }

    function start(){

        // the imgs[] array now holds fully loaded images
        // the imgs[] are in the same order as imageURLs[]

        // clip image#1
        clippingPath([10,70,50,10,90,70],imgs[0],10,10);

        // clip image#2
        clippingPath([10,170,50,110,90,170],imgs[1],10,110);

        // append the original images for demo purposes
        document.body.appendChild(imgs[0]);
        document.body.appendChild(imgs[1]);

    }

    function clippingPath(pathPoints,img,x,y){

        // save the unclipped context
        ctx.save();

        // define the path that will be clipped to
        ctx.beginPath();
        ctx.moveTo(pathPoints[0],pathPoints[1]);
        // this demo has a known number of polygon points
        // but include a loop of "lineTo's" if you have a variable number of points
        ctx.lineTo(pathPoints[2],pathPoints[3]);
        ctx.lineTo(pathPoints[4],pathPoints[5]);
        ctx.closePath();    

        // stroke the path
        // half of the stroke is outside the path
        // the outside part of the stroke will survive the clipping that follows
        ctx.lineWidth=2;
        ctx.stroke();

        // make the current path a clipping path
        ctx.clip();

        // draw the image which will be clipped except in the clipping path
        ctx.drawImage(img,x,y);

        // restore the unclipped context (==undo the clipping path)
        ctx.restore();
    }


}); // end $(function(){});
</script>
</head>
<body>
    <p>Images clipped inside triangular canvas paths</p>
    <canvas id="canvas" width=150 height=200></canvas>
    <p>Original Images</p>
</body>
</html>
markE
  • 102,905
  • 11
  • 164
  • 176
  • Thanks tentatively. I wil have to process it :) – user3334085 Sep 06 '14 at 17:19
  • 1
    For now I got the 2px width line half of which will survice the clipping. But the callback I must read up on before I can try this myself and thank you properly :) – user3334085 Sep 06 '14 at 17:29
  • It looks like start () is the main proper. We have just put all of the program into an image loader? Please this one is a real question not a rhetorical one. – user3334085 Sep 06 '14 at 18:17
  • Now it works, of course. Thank you. But my this callback thing is one ugly thing. What is one supposed to CALL those calling functions, loadImagesAndThenContinueIfAllRight () ? :d – user3334085 Sep 06 '14 at 18:25
  • Yes, the image loader takes time to complete the loading process and then it calls start(). So, yes, start() is like the main() function. Traditionally you (1) create global vars before the image loader, (2) do the image loader, (3) In start(), set the previously created global vars, (4) now that everything is configured, execute code that does your clipping/drawing. – markE Sep 06 '14 at 18:26
  • One final thought on "ugly" start() code--and yes, it can get ugly! You might check out javascript pseudo-classes as a way to encapsulate code into "beautiful" components. ;-) – markE Sep 06 '14 at 19:23
  • One more thought. The thing that struck me as "ugly" (confusing) was acually not the callback; it was putting everything in the image onload. But then this thread: http://stackoverflow.com/questions/588040/window-onload-vs-document-onload says that window,onload waits for all the images to load (so you don't even need a counter) and for all the dom AS WELL. So I just put everything into window onload, layout setup and all, and left the images out in the global. That works too and seems less confusing to me. Are there insidious disadvantages? – user3334085 Sep 09 '14 at 09:07
  • @user3334085. Keep in mind that window.onload waits until all *img elements on the page* are loaded. If you load images independently of the DOM (like using `new Image`) then window.onload is unaware and will not wait until those images are loaded. – markE Sep 09 '14 at 16:56
0

@markE

Images in example are gone. I cut them from screenshot above and uploaded to our own site and clones/updated the fiddle: http://jsfiddle.net/6gawtudc/

I can't comment on previous answer because of lack of reputation points. please someone update the answer or reupload the images

imageURLs.push("https://images.v3.webhome.nl/flags_for_polygon/norway.png");
imageURLs.push("https://images.v3.webhome.nl/flags_for_polygon/sweden.png");
Jvo
  • 46
  • 4