Proper way to approach loadable masks in Fabric.js

2019-06-09 07:46发布

问题:

I have this bounty open Fabricjs mask object with transformation when trying to mask objects with Fabric.js.

The tool I'm developing should allow users to draw a mask over image objects, and apply transformations (skew scale rotate etc) to this object before or after the mask. I'm close to obtaining this result but objects with an angle are still not working.

I'm also trying to save this object to a database using toJSON and loadFromJSON, but after a few days trying to accomplish this I realize that this solution will not work because any references outside the ctx scope can't be accessed while loading from JSON, so they throw an error.

    clipTo: function(ctx) {
        mask.set({
            left:
                -object.width / 2 -
                (mask.width / 2) * originalMaskScaleX -
                originalObjLeft / originalObjScaleX,
            top:
                -object.height / 2 -
                (mask.height / 2) * originalMaskScaleY -
                originalObjTop / originalObjScaleY,
            objectCaching: false
        });
        mask.render(ctx);
    }

Is Fabric.js the proper solution to this problem? Should I be using something else? If this can be done with Fabric.js, what is the proper approach?

回答1:

I extended fabric.Image with some custom attributes. Also I attached the mask on fabric.Image. For fabric.Image.fromObject after the image is loaded I need it to load also the mask( which I know is a path) and attach to image. This is a fast implementation. I'm pretty sure this code can be simplified. Please tell me know if something is not clear enougth

 

    canvas = new fabric.Canvas("canvas", {
   backgroundColor: "lightgray",
   width: 1280,
   height: 720,
   preserveObjectStacking: true,
   selection: false,
   stateful: true
 });

 canvas.isDrawingMode = true;
 canvas.freeDrawingBrush.color = "black";
 canvas.freeDrawingBrush.width = 2;

 canvas.on("path:created", function(options) {

   clip(options.path);
 });

 function clip(path) {
   canvas.isDrawingMode = false;
   canvas.remove(path);

   let mask = new fabric.Path(path.path, {
     top: object.top,
     left: object.left,
     objectCaching: false,
     strokeWidth: 0,
     scaleX: 1 / object.scaleX,
     scaleY: 1 / object.scaleY,
     pathOffset: {
       x: 0,
       y: 0
     }
   });
   object = canvas.getObjects()[0];
   object.originalObjLeft = object.left,
     object.originalObjTop = object.top,
     object.originalMaskScaleX = mask.scaleX,
     object.originalMaskScaleY = mask.scaleY,
     object.originalObjScaleX = object.scaleX,
     object.originalObjScaleY = object.scaleY;
     var transformedTranslate = object.translateToGivenOrigin({
        x: object.left,
        y: object.top
    }, object.originX, object.originY, 'center', 'center');
    object.originalTransformLeft = transformedTranslate.x - object.getCenterPoint().x;
    object.originalTransformTop = transformedTranslate.y - object.getCenterPoint().y;
    object.originalAngle = object.angle;
    
    
   object.clipMask = mask;
   object.set({
     clipTo: function(ctx) {
			 
        ctx.save();
        ctx.rotate(-this.originalAngle * Math.PI / 180);

        ctx.translate(this.originalTransformLeft / this.originalObjScaleX, this.originalTransformTop / this.originalObjScaleY)

       
       
       this.clipMask.set({
         left: -object.width / 2 - (this.clipMask.width / 2 * this.originalMaskScaleX) - this.originalObjLeft / this.originalObjScaleX,
         top: -object.height / 2 - (this.clipMask.height / 2 * this.originalMaskScaleY) - this.originalObjTop / this.originalObjScaleY,
         objectCaching: false
       });
       this.clipMask.render(ctx);
        ctx.restore();
     }
   });

   canvas.requestRenderAll();
 }

 // image

 let image = new Image();


 image.onload = function() {
   object = new fabric.Image(image, {
     width: 500,
     height: 500,
     scaleX: 0.8,
     scaleY: 0.8,
      angle: 45,
     top: 50,
     left: 100
   });

   canvas.add(object);
 };

 image.src = "http://i.imgur.com/8rmMZI3.jpg";

 fabric.util.object.extend(fabric.Image.prototype, {
   clipMask: null,
   originalObjLeft: 0,
   originalObjTop: 0,
   originalMaskScaleX: 1,
   originalMaskScaleY: 1,
   originalObjScaleX: 1,
   originalObjScaleY: 1,
   originalAngle:0,
   originalTransformLeft:0,
   originalTransformTop:0
 });
 fabric.Image.prototype.toObject = (function(toObject) {
   return function(propertiesToInclude) {
     return fabric.util.object.extend(toObject.call(this, propertiesToInclude), {
       clipMask: this.clipMask ? this.clipMask.toObject(propertiesToInclude) : null,
       originalObjLeft: this.originalObjLeft,
       originalObjTop: this.originalObjTop,
       originalMaskScaleX: this.originalMaskScaleX,
       originalMaskScaleY: this.originalMaskScaleY,
       originalObjScaleX: this.originalObjScaleX,
       originalObjScaleY: this.originalObjScaleY,
       originalAngle:this.originalAngle,
       originalTransformLeft:this.originalTransformLeft,
       originalTransformTop:this.originalTransformTop
     });
   }
 })(fabric.Image.prototype.toObject);

 fabric.Image.fromObject = (function(fromObject) {
   return function(_object, callback) {
     fromObject.call(this, _object, (function(callback, _object) {
       return function(image) {
         if (image.clipMask) {
           fabric.Path.fromObject(image.clipMask, (function(callback) {
             return function(path) {
               path.pathOffset.x = 0;
               path.pathOffset.y = 0;
               image.clipMask = path;
               callback(image);
             }
           })(callback))
         } else {
           callback(image);
         }
       }
     })(callback, _object));
     return;
   }
 })(fabric.Image.fromObject)




 $("#button1").on('click', function() {
   let dataJSON = canvas.toJSON();
   canvas.clear();
   canvas.loadFromJSON(
     dataJSON,
     canvas.renderAll.bind(canvas));
 })
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<button id="button1">SAve/Load JSON</button>
<div class="canvas__wrapper">
  <canvas id="canvas" width="1280" height="720"></canvas>
</div>

UPDATE I updated the code to fix the problem with angle from here:



标签: fabricjs