skip to Main Content

I’m currently working on web app for photo editing using FabricJS and one of features I need to implement is something like Clipping masks from Photoshop.

For example I have this assets: frame, mask and image. I need to insert image inside frame and clip it with mask. Most tricky part is in requirements:

  1. User should be able to modify image inside frame, e.g. move, rotate, skew… Frame itself also can be moved inside canvas.
  2. Number of layers is not limited so user can add objects under or above masked image.
  3. Masks, frames and images is not predefined, user should be able to upload and use new assets.

My current solution is this:

  1. Load assets
  2. Set globalCompositeOperation of image to source-out
  3. Set clipTo function for image.
  4. Add assets on canvas as a group

In this solution clipTo function preserve image inside rectangular area of frame and with help of globalCompositeOperation I’m clipping image to actual mask. At first sight it works fine but if I add new layer above this newly added group it will be cutted off because of globalCompositeOperation="source-out" rule. I’ve created JSFiddle to show this.

So, that else could I try? I’ve seen some posts on StackOverflow with advices to use SVGs for clipping mask, but if I understand it correctly SVG must contain only one path. This could be a problem because of third requirement of my app.

Any advice in right direction will help, because right now I’m totally stuck with this problem.

3

Answers


  1. Not sure what you want.

    If you want the last image loaded (named img2), the one you send to the back to not effect the layers above do the following.

    You have mask,frame,img, and img2;

    Put them in the following order and with the following comp settings.

    • img2, source-over
    • img, source-over
    • mask, destination-out
    • frame, source-over

    If you want something else you will have to explain it in more detail.

    Personally when I provide masking to the client I give them full access to all the composite methods and allow them to work out what they need to do to achieve a desired effect. Providing a UI that allows you to change the comp setting, and layer order makes it a lot easier to sort out the sometimes confusing canvas composite rules.

    Login or Signup to reply.
  2. I’d suggest looking at this solution.

    Multiple clipping areas on Fabric.js canvas

    You end up with a shape layer that is used to define the mask shape. That shape then gets applied as a clipTo to your image.

    The one limitation I can think off though that you might run into is when you start to rotate various shapes. I know I have it working great with a rectangle and a circle, however ran into some issues with polygons from what I recall… This was all setup under and older version of FabricJS however, so there may have been some improvements there that I’m not experienced with.

    The other issue I ran into was drop shadows didn’t render correctly when passed to a NodeJS server running FabricJS.

    Login or Signup to reply.
  3. You can do this by using ClipPath property of Img Object which you want to mask. With this, you can Mask Any Type of Object. and also you need to add some Ctx Configuration in ClipTo function of Img Object.
    check this link https://jsfiddle.net/naimsajjad/8w7hye2v/8/

    (function() {
      var img01URL = 'http://fabricjs.com/assets/printio.png';
      var img02URL = 'http://fabricjs.com/lib/pug.jpg';
      var img03URL = 'http://fabricjs.com/assets/ladybug.png';
      var img03URL = 'http://fabricjs.com/assets/ladybug.png';
      var canvas = new fabric.Canvas('c');
      canvas.backgroundColor = "red";
      canvas.setHeight(500);
      canvas.setWidth(500);
    
      canvas.setZoom(1)
      var circle = new fabric.Circle({radius: 40, top: 50, left: 50, fixed: true, fill: '', stroke: '1' });
      canvas.add(circle);
      canvas.renderAll();
    
      fabric.Image.fromURL(img01URL, function(oImg) {
        oImg.scale(.25);
        oImg.left = 10;
        oImg.top = 10;
        oImg.clipPath = circle;
        oImg.clipTo = function(ctx) {
          clipObject(this,ctx)
        }
        canvas.add(oImg);
        canvas.renderAll();
      });
      var bili = new fabric.Path('M85.6,606.2c-13.2,54.5-3.9,95.7,23.3,130.7c27.2,35-3.1,55.2-25.7,66.1C60.7,814,52.2,821,50.6,836.5c-1.6,15.6,19.5,76.3,29.6,86.4c10.1,10.1,32.7,31.9,47.5,54.5c14.8,22.6,34.2,7.8,34.2,7.8c14,10.9,28,0,28,0c24.9,11.7,39.7-4.7,39.7-4.7c12.4-14.8-14-30.3-14-30.3c-16.3-28.8-28.8-5.4-33.5-11.7s-8.6-7-33.5-35.8c-24.9-28.8,39.7-19.5,62.2-24.9c22.6-5.4,65.4-34.2,65.4-34.2c0,34.2,11.7,28.8,28.8,46.7c17.1,17.9,24.9,29.6,47.5,38.9c22.6,9.3,33.5,7.8,53.7,21c20.2,13.2,62.2,10.9,62.2,10.9c18.7,6.2,36.6,0,36.6,0c45.1,0,26.5-15.6,10.1-36.6c-16.3-21-49-3.1-63.8-13.2c-14.8-10.1-51.4-25.7-70-36.6c-18.7-10.9,0-30.3,0-48.2c0-17.9,14-31.9,14-31.9h72.4c0,0,56-3.9,70.8,26.5c14.8,30.3,37.3,36.6,38.1,52.9c0.8,16.3-13.2,17.9-13.2,17.9c-31.1-8.6-31.9,41.2-31.9,41.2c38.1,50.6,112-21,112-21c85.6-7.8,79.4-133.8,79.4-133.8c17.1-12.4,44.4-45.1,62.2-74.7c17.9-29.6,68.5-52.1,113.6-30.3c45.1,21.8,52.9-14.8,52.9-14.8c15.6,2.3,20.2-17.9,20.2-17.9c20.2-22.6-15.6-28-16.3-84c-0.8-56-47.5-66.1-45.1-82.5c2.3-16.3,49.8-68.5,38.1-63.8c-10.2,4.1-53,25.3-63.7,30.7c-0.4-1.4-1.1-3.4-2.5-6.6c-6.2-14-74.7,30.3-74.7,30.3s-108.5,64.2-129.6,68.9c-21,4.7-18.7-9.3-44.3-7c-25.7,2.3-38.5,4.7-154.1-44.4c-115.6-49-326,29.8-326,29.8s-168.1-267.9-28-383.4C265.8,13,78.4-83.3,32.9,168.8C-12.6,420.9,98.9,551.7,85.6,606.2z',{top: 0, left: 180, fixed: true, fill: 'white', stroke: '', scaleX: 0.2, scaleY: 0.2 });
      canvas.add(bili);
      canvas.renderAll();
      fabric.Image.fromURL(img02URL, function(oImg) {
        oImg.scale(0.5);
        oImg.left = 180;
        oImg.top = 0;
        oImg.clipPath = bili;
        oImg.clipTo = function(ctx) {
          clipObject(this,ctx)
        }
        canvas.add(oImg);
        canvas.renderAll();
      });
    
      function clipObject(thisObj,ctx)
      {
        if (thisObj.clipPath) {
          ctx.save();
          if (thisObj.clipPath.fixed) {
            var retina = thisObj.canvas.getRetinaScaling();
            ctx.setTransform(retina, 0, 0, retina, 0, 0);
              // to handle zoom
              ctx.transform.apply(ctx, thisObj.canvas.viewportTransform);
              thisObj.clipPath.transform(ctx);
            }
            
            thisObj.clipPath._render(ctx);
            ctx.restore();
            ctx.clip();
            var x = -thisObj.width / 2, y = -thisObj.height / 2, elementToDraw;
    
            if (thisObj.isMoving === false && thisObj.resizeFilter && thisObj._needsResize()) {
              thisObj._lastScaleX = thisObj.scaleX;
              thisObj._lastScaleY = thisObj.scaleY;
              thisObj.applyResizeFilters();
            }
            elementToDraw = thisObj._element;
            elementToDraw && ctx.drawImage(elementToDraw,
             0, 0, thisObj.width, thisObj.height,
             x, y, thisObj.width, thisObj.height);
            thisObj._stroke(ctx);
            thisObj._renderStroke(ctx);
          }
        }
      })();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js"></script>
    <canvas id="c" width="400" height="400"></canvas>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search