Drawing overlapping circles with alpha channeling

前端 未结 2 452
别那么骄傲
别那么骄傲 2021-01-24 13:41

This question has sort of been answered here: Combined area of overlapping circles

My problem is more specific though. I have an arbitrary number of arbitrarily sized ci

相关标签:
2条回答
  • 2021-01-24 14:33

    It is possible to do natively without using pixel manipulation or any library.

    Provided the transparency is the same for all the circles it is pretty straight forward.

    Solution

    snapshot
    Circles on top of some random background

    What you need to do is to:

    • Allocate an off-screen canvas where circles are drawn as solids (no transparency)
    • Draw the circles in three steps.
    • All the circles' red surface first, then all the circles' blue surface and so on.
    • Set global alpha for transparency for main (visible) canvas
    • Clear both canvases
    • Draw off-screen canvas to main canvas

    Your circle function can look something like this:

    function drawCircle(x, y, r, step) {
    
        ctx.beginPath();
    
        switch (step) {
            case 0:  // step 0, outer circle red
                ctx.fillStyle = '#f00';
                break;
    
            case 1:  // step 1, middle circle blue
                ctx.fillStyle = '#00f';
                r *= 0.67;
                break;
    
            case 2:  // step 2, inner circle green
                ctx.fillStyle = '#0f0';
                r *= 0.33;
                break;
        }
        ctx.arc(x, y, r, 0, 2 * Math.PI);
        ctx.fill();
    }
    

    The function takes the x and y center point as well as radius. But in addition it takes a step value between 0 and 2 which determines which surface is being drawn. This will be important in the next steps.

    First we can define an array holding all the circles we want to draw:

    var circs = [
        //x   y    r   dx dy (the last two for animation only)
        [100, 100, 50, 2, 1],
        [200, 200, 50, -2, -3],
        [150, 50, 50, 3, -1]
    ];
    

    From here you would drag them, offset x and y and then redraw them, but for demo's sake I'll animate them.

    Before we draw we set the global alpha on the main canvas (the off-screen is kept solid):

    mctx.globalAlpha = 0.7;  // main canvas
    

    And the animation loop:

    function start() {
        
        // clear off-screen canvas
        ctx.clearRect(0,0, w, h);
    
        // clear main canvas
        mctx.clearRect(0,0, w, h);
    
        var t = 0, i, c;
    
        // outer step loop
        for(; t < 3; t++) {
    
            // draw all circles at current step
            for(i = 0; c = circs[i]; i++) {
                drawCircle(c[0], c[1], c[2], t);
            }
        }
    
        // re-position circles for animation
        for(i = 0;c = circs[i]; i++) {
            c[0] += c[3];  /// add delta to x
            c[1] += c[4];  /// add delta to y
    
            // reverse deltas if at boundaries
            if (c[0] < 0 || c[0] > w) c[3] = -c[3];
            if (c[1] < 0 || c[1] > h) c[4] = -c[4];
        }
    
        // draw off-screen to main canvas        
        mctx.drawImage(ocanvas, 0, 0);
    
        // loop animation
        requestAnimationFrame(start);
    }
    

    The global alpha can be reset for each operation in case you want to draw other elements to the canvas - or use a second on-screen canvas to hold static content.

    Demo

    var demo = document.getElementById("demo");
    var w = demo.width, h = demo.height;
    
    var ocanvas = document.createElement('canvas');
    ocanvas.width = w;
    ocanvas.height = h;
    
    var ctx = ocanvas.getContext('2d');
    var mctx = demo.getContext('2d');
    var img = document.createElement('img')
    img.onload = start;
    img.src = 'http://i.imgur.com/CHPdL2y.png';
    
    /// key to it all
    mctx.globalAlpha = 0.7;
    
    var circs = [
        //x   y    r   dx   dy
        [100, 100, 50,  2  ,  1.5],
        [200, 200, 70, -2  , -3],
        [150,  50, 50,  3  , -1],
        [150,  50, 30,  4  ,  4],
        [150,  50, 20, -3  , -2],
        [100, 100, 55,  2.5,  2.5],
        [200, 200, 75, -1  , -2.5],
        [150,  50, 45,  3.5, -2],
        [150,  50, 35,  5  ,  2],
        [150,  50, 25, -1.2, -5]
    ];
    
    function drawCircle(x, y, r, step) {
    
        ctx.beginPath();
    
        switch (step) {
            case 0:
                ctx.fillStyle = '#f00';
                break;
            case 1:
                ctx.fillStyle = '#00f';
                r *= 0.67;
                break;
            case 2:
                ctx.fillStyle = '#0f0';
                r *= 0.33;
                break;
        }
        ctx.arc(x, y, r, 0, 2 * Math.PI);
        ctx.fill();
    }
    
    function start() {
        
        ctx.clearRect(0, 0, w, h);
        mctx.clearRect(0, 0, w, h);
    
        var i = 0, t, c;
        for(t = 0; t < 3; t++) {
            for(i = 0; c = circs[i]; i++) {
                drawCircle(c[0], c[1], c[2], t);
            }
        }
    
        for(i = 0;c = circs[i]; i++) {
            c[0] += c[3];
            c[1] += c[4];
            if (c[0] < 0 || c[0] > w) c[3] = -c[3];
            if (c[1] < 0 || c[1] > h) c[4] = -c[4];
        }
        
        mctx.drawImage(ocanvas, 0, 0);
        requestAnimationFrame(start);
    }
    body {
        margin:0;
        background:url(//i.stack.imgur.com/b8eCZ.jpg) no-repeat;
    }
    <canvas id="demo" width="500" height="333"></canvas>

    0 讨论(0)
  • 2021-01-24 14:37

    You must use a library like RaphaelJS. It is capable of more than what you ask for here.

    Edit: You can apply alpha transparency through js by programmatically determining the distance between centres (D) and comparing it with the sum of the radii (S). If D < S, then they overlap so take care of red circles.

    (http://raphaeljs.com/)

    0 讨论(0)
提交回复
热议问题