Search code examples
javascriptsvgfabricjs

FabricJs - Clipping area by SVG object


I have a problem with FabricJS objects. I would like to create a clipping area (svg object), which will clips loaded images.

I'm using clipTo method on loaded image passing it into clipping area shape object (SVG), but it does not work, because the SVG is not shape object in sense of FabricJS. Is there any way to convert the loaded SVG into FabricJS shape like Polygon or Rect?

It almost works good if I will use:

globalCompositeOperation = 'source-atop' 

on the loaded image (and remove clipTo function), but if I will move/resize/rotate the image it overlaps border of the main clipping area (the biggest rect). Image should not interact with other objects outside the grey rect, especially with main clipping area. Maybe is there a way to achieve this by playing with globalCompositeOperation?

I will be very grateful for any tips.

Here is JSFiddle of my problem: https://jsfiddle.net/6nfdr1ng/

and here almost working version (with source-atop): https://jsfiddle.net/ud9nev1g/

<input type="file" id="image"><br>
<canvas id="canvas" width="530" height="600"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.21/fabric.min.js"></script>
<script type="text/javascript">
    var canvas = new fabric.Canvas('canvas', {
        'selection': false
    });

    /**
     * loading man clipping rect
     */
    var clippingRect = new fabric.Rect({
        width: 185,
        height: 400,
        fill: 'transparent',
        stroke: 1,
        opacity: 1,
        hasBorders: false,
        hasControls: false,
        hasRotatingPoint: false,
        selectable: false,
        preserveObjectStacking: true,
        objectCaching: false
    });
    canvas.add(clippingRect);
    canvas.renderAll();

    /**
     * loading shape which should be another clipping area for loaded image
     */
    var svgString = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 63.7 63.7"><defs><style>.cls-1{fill:#79797a;fill-rule:evenodd;}</style></defs><title>Asset 4</title><g id="Layer_2" data-name="Layer 2"><g id="kwadrat"><path class="cls-1" d="M0,0H63.7V63.7H0Z"/></g></g></svg>';
    var retinaScalling = canvas.getRetinaScaling();
    fabric.loadSVGFromString(svgString, function (objects, options) {
        var shapeObject = fabric.util.groupSVGElements(objects, options);
        shapeObject.scaleX = 0.5;
        shapeObject.scaleY = 0.5;
        shapeObject.set('id', 'shape');
        shapeObject.scaleToWidth(clippingRect.getWidth());
        shapeObject.setCoords();
        shapeObject.clipTo = function (ctx) {
            ctx.save();
            ctx.setTransform(retinaScalling, 0, 0, retinaScalling, 0, 0);
            clippingRect.render(ctx);
            ctx.restore();
        };
        shapeObject.center();
        shapeObject.setCoords();
        canvas.setActiveObject(shapeObject);
        canvas.add(shapeObject);
        canvas.renderAll();
    });

    /**
     * loading image
     */
    document.getElementById('image').onchange = function (e) {
        var reader = new FileReader();
        var clippingAreaShape = canvas.getActiveObject();
        reader.onload = function (event) {
            var img = new Image();
            img.onload = function () {
                var shapeImageObject = new fabric.Image(img, {
                    objectCaching: false,
                    hasControls: true,
                    selectable: true
                });
                shapeImageObject.scaleToHeight(clippingAreaShape.getWidth());
                shapeImageObject.set('id', 'shape-image');
                shapeImageObject.set({
                    left: clippingAreaShape.left,
                    top: clippingAreaShape.top
                });
                shapeImageObject.clipTo = function (ctx) {
                    ctx.save();
                    ctx.setTransform(retinaScalling, 0, 0, retinaScalling, 0, 0);
                    clippingAreaShape.render(ctx);
                    ctx.restore();
                };
                // shapeImageObject.globalCompositeOperation = 'source-atop';
                canvas.add(shapeImageObject);
                canvas.renderAll();
                canvas.setActiveObject(shapeImageObject);
                shapeImageObject.setCoords();
            };
            img.src = event.target.result;
        };
        reader.readAsDataURL(e.target.files[0]);
    }

</script>

Solution

  • I resolved my problem by combining the SVG shape into one path (without any polygons any other svg objects), then I pass the svg coords into fabircJS path. The valid SVG coords looks like that "M0,0H63.7V63.7H0Z"

    fabric.loadSVGFromURL('object.svg', function (objects, options) {
                let img1 = new fabric.Path(objects[0].d, {
                    fill: '#333',
                    opacity: 1,
                    hasBorders: true,
                    hasControls: true,
                    hasRotatingPoint: true,
                    selectable: true,
                    preserveObjectStacking: true,
                    objectCaching: false,
                });
         }
    

    I hope it will help someone else :)