Francisc0
Francisc0

Reputation: 1018

how to split a canvas diagonally or non-square pieces

Say I have a canvas that's a rectangle. I want to take that canvas and split it diagonally and be able to manipulate those pieces to do whatever I want.

My end goal is to have a rectangle split diagonally and the 2 pieces animate off screen in opposite directions. I was thinking of doing this entirely within the canvas in an animation loop or converting each segment into an image element and using CSS3 animations to move those pieces. I'm just trying to figure out the best way to do this.

The code, codepen link, and image below are just to illustrate where I want my canvas to be split. You'll see it's not an exact split with equal sides.

http://codepen.io/FranciscoG/pen/YPjzbQ

<div id="container">
  <img id="dog" src="http://i.imgur.com/1GUzYh9.jpg" width="375"
  height="667">
</div>

<script>
var container = document.getElementById('container');
var dogImg = document.getElementById('dog');

function convertImageToCanvas(image) {
    var canvas = document.createElement("canvas");
    canvas.width = image.width;
    canvas.height = image.height;
    canvas.getContext("2d").drawImage(image, 0, 0);
    return canvas;
}

function drawLine(canvas) {
  var context = canvas.getContext('2d');
  context.beginPath();
  context.moveTo(0,0);
  context.lineTo(345, 0);
  context.lineTo(0, 567);
  context.lineTo(0,0);
  context.stroke();
  context.closePath();
  return canvas;
};

var newDog = convertImageToCanvas(dogImg);
var divided = drawLine(newDog);
container.innerHTML = "";
container.appendChild(divided)
</script>

enter image description here

Upvotes: 1

Views: 2393

Answers (2)

user1693593
user1693593

Reputation:

You could always use clipping but note that this would involve save/restore calls which is a relative slow business. There has been suggested to get resetClip() included in the specs but it seem to be hard to get the message across to the group why it is needed.

In any case, I would recommend the following approach:

  • Create an object or function that can reproduce a single mask (path) of one of the half-side of the image (ie. the diagonal line with containing box).
  • For left half: Draw image, set composite mode to destination-in, draw mask, extract canvas as image
  • For right half: Draw image, set composite mode to destination-out, draw mask, extract canvas as image

Now put the to images (just use the canvas elements directly) inside a container so that they are stacked on top of each other.

Animate using a transition or animation class.

var img = new Image(375, 667);
img.onload = setup;
img.src = "http://i.imgur.com/1GUzYh9.jpg";

function setup() {

  var path = [0,0, 345,0, 0, 567];   // last point not needed..
  var left = createCanvas(this, path, "destination-in");
  var right = createCanvas(this, path, "destination-out");
  var cont = document.getElementById("cont");
  
  cont.appendChild(left);
  cont.appendChild(right);
  
  // animate here by setting animation/transition class or using JS:
  var x = 0;
  (function loop() {
    left.style.left = x + "px";   // translate is smoother, but need
    right.style.left = -x + "px"; // prefix in some browser. Update as needed..
    x-=5; if (x < -400) x = 0;
    requestAnimationFrame(loop);
  })();
  
  function createCanvas(img, path, mode) {
    var canvas = document.createElement("canvas"),
        ctx = canvas.getContext("2d");
    canvas.width = img.width;
    canvas.height = img.height;
    
    // draw image
    ctx.drawImage(img, 0, 0);
    
    // create mask
    ctx.moveTo(path[0], path[1]);
    for(var i = 2; i < path.length; i += 2) ctx.lineTo(path[i], path[i+1]);
    ctx.closePath();
    
    // composite mode and create half
    ctx.globalCompositeOperation = mode;
    ctx.fill();
    
    return canvas
  }
}
#cont {
  position:relative;width:375px;height:667px;overflow:hidden;
  }
#cont>canvas {position:absolute;left:0;right:0;}
<div id="cont"></div>

Upvotes: 4

markE
markE

Reputation: 105045

You can use context.clip to achieive your image-splitting effect

context.clip restricts an image to being drawn withing a specified path.

You can define several of these clipping regions to divide your image into several pieces (paths).

Then in an animation loop, you can clear the canvas and redraw each of these clipping regions with an changing offset. The left clipping region will move (offset) leftward in each loop. The right clipping region will move (offset) rightward in each loop.

Here's example code and a Demo:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;


var nextTime=0;
var duration=1000/60*3;

var offset=0;
var paths=[];

var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/Dog-With-Cute-Cat.jpg";
function start(){
  cw=canvas.width=img.width;
  ch=canvas.height=img.height;

  paths.push({path:[{x:0,y:0},{x:150,y:0},{x:0,y:ch}],direction:-1});
  paths.push({path:[{x:150,y:0},{x:0,y:ch},{x:cw,y:ch},{x:cw,y:0}],direction:1});

  requestAnimationFrame(animate);
}

function draw(){
  ctx.clearRect(0,0,cw,ch);
  for(var i=0;i<paths.length;i++){
    var path=paths[i].path;
    var offX=offset*paths[i].direction;
    ctx.save();
    ctx.beginPath();
    var pt=path[0];
    ctx.moveTo(pt.x+offX,pt.y);
    for(var j=1;j<path.length;j++){
      var pt=path[j];
      ctx.lineTo(pt.x+offX,pt.y);
    }
    ctx.closePath();
    ctx.stroke();
    ctx.clip();
    ctx.drawImage(img,offX,0);
    ctx.restore();
  }
}

function animate(time){
  if(offset<cw){requestAnimationFrame(animate);}else{log('done');}
  if(time<nextTime){return;}
  nextTime=time+duration;
  draw();
  offset++;
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>

Upvotes: 1

Related Questions