29

I want to set a global clipTo in my Fabric-powered Canvas that will affect all user-added layers. I want a background image and an overlay image, which are unaffected by this clip mask.

Example:

enter image description here

Here's what's happening in this photo:

  1. A canvas overlay image makes the t-shirt look naturally wrinkled. This overlay image is mostly transparent
  2. A background image in the exact shape of the t-shirt was added, which is supposed to make the t-shirt look blue
  3. A canvas.clipTo function was added, which clips the canvas to a rectangular shape
  4. A user-added image (the famous Fabric pug) was added

I want the user-added image (the pug) to be limited to the rectangular area.

I do not want the background image (the blue t-shirt shape) affected by the clip area.

Is there a simple way to accomplish this? I really don't want to have to add a clipTo on every single user layer rather than one tidy global clipTo.

You can play with a JS fiddle showing the problem here.

chadoh
  • 4,343
  • 6
  • 39
  • 64
  • Have you ever got this working? – Rivka Jan 12 '16 at 17:52
  • @Rivka not in a way I liked. I got it working by adding a (very complex) `clipTo` to every single user layer, rather than one simple, global `clipTo`. – chadoh Jan 12 '16 at 21:09

4 Answers4

1

I came here with the same need and ultimately found a solution for what I'm working on. Maybe it helps:

For SVG paths, within the clipTo function you can modify the ctx directly prior to calling render(ctx) and these changes apply outside the clipped path o. Like so:

var clipPath = new fabric.Path("M 10 10 L 100 10 L 100 100 L 10 100", {
  fill: 'rgba(0,0,0,0)',
});

var backgroundColor = "rgba(0,0,0, 0.2)";

var opts = {
  controlsAboveOverlay: true,
  backgroundColor: 'rgb(255,255,255)',
  clipTo: function (ctx) {
    if (typeof backgroundColor !== 'undefined') {
      ctx.fillStyle = backgroundColor;
      ctx.fillRect(0, 0, 300, 150);
    }
    clipPath.render(ctx);
  }
}
var canvas = new fabric.Canvas('c', opts);

canvas.add(new fabric.Rect({
  width: 50,
  height: 50,
  left: 30,
  top: 30,
  fill: 'rgb(255,0,0)'
}));

You can of course add an image instead of a color, or whatever else you want done. The trick I've found is to put it in the clipTo function on the ctx directly.

here's a fiddle

Kyle Shike
  • 345
  • 1
  • 4
  • 12
  • As far as I can tell, this is for adding one simple rectangular clip area. I'm already able to add one global clip area. It works great. The trouble trouble is that it clips the background shape, though. I want a global clip area that does not clip the background shape. – chadoh Nov 18 '15 at 15:49
  • I'm pretty sure we're talking about the same thing. By modifying the ctx within the clipTo function prior to rendering from the clip path (which is arbitrary btw -In this example it's a simple rectangle but it can be any svg shape you want), you can draw to the canvas outside the global clip area. So in the fiddle example, you could draw an image rather than the grey bg, or in your case draw the the t-shirt and the t-shirt blue overlay and they will not be affected by the global clip. [See here](https://jsfiddle.net/v3ustgk9/4/) – Kyle Shike Nov 18 '15 at 21:27
0

One (sorta hacky) solution: set a CSS background image on your canvas element, as shown in https://jsfiddle.net/qpnvo3cL/

<canvas id="c" width="500" height="500"></canvas>
<style>
  background: url('http://fabricjs.com/assets/jail_cell_bars.png') no-repeat;
</style>
<script>
  var canvas = window._canvas = new fabric.Canvas('c');
  canvas.clipTo = function(ctx) {
      ctx.rect(100,100,100,100);
  }
</script>
chadoh
  • 4,343
  • 6
  • 39
  • 64
  • This has at least two issues: 1) if using an SVG as background image and changing its color, you will need to somehow use this dynamically-generated svg as the background image in your css—perhaps you can surmount this by exporting it as an inline data image. 2) When exporting your final image from Fabric, your CSS styles will of course NOT be included, so your shirt color will be wrong in the generated image. – chadoh Oct 07 '15 at 18:07
0

Have you tried clipping a fabric Group? You could make the whole shirt one canvas. The center graphics would be one Group which you clip to where you want it. The white t-shirt and the blue overlay would of course not be part of the clipped group.

Here's an example of clipping a group:

var rect = new fabric.Rect({width:100, height: 100, fill: 'red' });
var circle = new fabric.Circle({ radius: 100, fill: 'green' });
var group1 = new fabric.Group([ circle, rect ], { left: 100, top: 100 });
canvas.add(group1);

group1.clipTo = function(ctx) {
    ctx.rect(50,50,200,200);
};

See this jsfiddle I made: https://jsfiddle.net/uvepfag5/4/

Stafree
  • 119
  • 5
-1

I find clip rather slow so I tend to use globalCompositeOperation to do masking.

If you really need to use clip then use it in conjunction with save and restore.

// ctx is canvas context 2d
// pug is the image to be clipped

// draw your background
ctx.save(); // save state
ctx.rect(100,100,100,100); // set the clip area
ctx.clip(); // apply the clip 
ctx.drawImage(pug,x,y); // draw the clipped image
ctx.restore(); // remove the clipping
// draw the other layers.

or you can

// draw background
ctx.globalCompositeOperation = "xor";  // set up the mask
ctx.fillRect(100,100,100,100); // draw the mask, could be an image. 
                               // Alpha will effect the amount of masking,
                               // not available with clip 
ctx.globalCompositeOperation = "destination-over";
ctx.drawImage(pug,x,y); // draw the image that is masked                        
ctx.globalCompositeOperation = "source-over";
// draw the stuff that needs to be over everything.

The advantage of composite operations is you have control over the clipping at a per pixel level, including the amount of clipping via the pixel alpha value

Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • Thanks! It looks like you answered as if I'm drawing on the canvas manually. But I am not. I am using FabricJS to do most of the drawing. Now, I'm pretty new at all this canvas stuff, so maybe I misunderstand! Did you look at my JSfiddle? Can you demonstrate this approach with a fork of that? I don't understand how to apply what you've told me to the situation I described... – chadoh Oct 14 '15 at 14:01