peerbolte
peerbolte

Reputation: 1199

FabricJS Clipping and SVG Export

I have a small fabricjs scene which loads images and clips them to SVG shapes that are defined in an SVG layout. The clipping is done using the code that is specified in this stack.

I have two issues:

When I export to SVG (see the "export to SVG" button), the clipping does not persist. Is this possible, perhaps to export to SVG maybe by converting the clippings to in SVG?

I have a SVG string inside the let clippingSVG variable. Is it possible to apply this SVG as another clipPath for the complete canvas (including export possibility), or the group of images?

Thanks in advance

Upvotes: 2

Views: 3671

Answers (2)

PPrice
PPrice

Reputation: 2363

Thank you AndreaBogazzi for starting me off on the right foot. I'd prefer to use a subclass of fabric.Image rather than replace a couple of its prototype methods. Cut-and-paste can be dangerous; in fact, the newest version of fabricjs is already incompatible with the that solution. So, here's a subclass version of the same solution. In my case I'm always using a fixed clipPath so I removed all references to fixed.

const ClippedImage = fabric.util.createClass(fabric.Image, {
    type: 'ClippedImage',
    initialized: function(options) {
        options || (options = {})

        this.callSuper('initialize', options)
    },
    _render: function(ctx) {
        if (this.clipPath) {
            ctx.save()
            var retina = this.canvas.getRetinaScaling()
            ctx.setTransform(retina, 0, 0, retina, 0, 0)
            ctx.transform.apply(ctx, this.canvas.viewportTransform)
            this.clipPath.transform(ctx)
            this.clipPath._render(ctx)
            ctx.restore()
            ctx.clip()
        }                    
        this.callSuper('_render', ctx)
    },
    toSVG: function(reviver) {
        let result = this.callSuper('toSVG')
        if(this.clipPath) {
            const clipId = `Clipped${fabric.Object.__uid++}`
            result = `
                <clipPath id="${clipId}">
                    ${this.clipPath.toSVG(reviver)}
                </clipPath>
                <g clip-path="url(#${clipId})">${result}</g>`
        }
        return reviver ? reviver(result) : result
    }
})

Upvotes: 0

AndreaBogazzi
AndreaBogazzi

Reputation: 14731

This is rough implementation of top of the other answer that propose a toSVG method change to make clip ( fixed ) respected in toSVG.

The non fixed case is harder, i hope this helps.

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';

function toSvg() {
 document.getElementById('svg').innerHTML = canvas.toSVG();
}

    fabric.Image.prototype.toSVG = function(reviver) {
      var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2, clipUrl = '';
      if (this.clipPath) {
        var id = fabric.Object.__uid++;
        if (this.clipPath.fixed) {
          markup.push('<clipPath id="myClip' + id + '">\n',
          this.clipPath.toSVG(reviver),
          '</clipPath>\n');
        }
        clipUrl = ' clip-path="url(#myClip' + id + ')" ';
      }
      markup.push('<g ', clipUrl, '>',
        '<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '"', '>\n',
          '\t<image ', this.getSvgId(), 'xlink:href="', this.getSvgSrc(true),
            '" x="', x, '" y="', y,
            '" style="', this.getSvgStyles(),
            // we're essentially moving origin of transformation from top/left corner to the center of the shape
            // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left
            // so that object's center aligns with container's left/top
            '" width="', this.width,
            '" height="', this.height,
          '"></image>\n'
      );

      if (this.stroke || this.strokeDashArray) {
        var origFill = this.fill;
        this.fill = null;
        markup.push(
          '<rect ',
            'x="', x, '" y="', y,
            '" width="', this.width, '" height="', this.height,
            '" style="', this.getSvgStyles(),
          '"/>\n'
        );
        this.fill = origFill;
      }

      markup.push('</g></g>\n');

      return reviver ? reviver(markup.join('')) : markup.join('');
    };

    fabric.Image.prototype._render = function(ctx) {
      // custom clip code
      if (this.clipPath) {
        ctx.save();
        if (this.clipPath.fixed) {
          var retina = this.canvas.getRetinaScaling();
          ctx.setTransform(retina, 0, 0, retina, 0, 0);
          // to handle zoom
          ctx.transform.apply(ctx, this.canvas.viewportTransform);
          //
          this.clipPath.transform(ctx);
        }
        this.clipPath._render(ctx);
        ctx.restore();
        ctx.clip();
      }
      
      // end custom clip code
    
    
      var x = -this.width / 2, y = -this.height / 2, elementToDraw;

      if (this.isMoving === false && this.resizeFilter && this._needsResize()) {
        this._lastScaleX = this.scaleX;
        this._lastScaleY = this.scaleY;
        this.applyResizeFilters();
      }
      elementToDraw = this._element;
      elementToDraw && ctx.drawImage(elementToDraw,
                                     0, 0, this.width, this.height,
                                     x, y, this.width, this.height);
      this._stroke(ctx);
      this._renderStroke(ctx);
    };

var canvas = new fabric.Canvas('c');
canvas.setZoom(0.5)
fabric.Image.fromURL(img01URL, function(oImg) {
    oImg.scale(.25);
    oImg.left = 10;
    oImg.top = 10;
    oImg.clipPath = new fabric.Circle({radius: 40, top: 50, left: 50, fixed: true, fill: '', stroke: '' });
    canvas.add(oImg);
    canvas.renderAll();
});

fabric.Image.fromURL(img02URL, function(oImg) {
    oImg.scale(.40);
    oImg.left = 180;
    oImg.top = 0;
    oImg.clipPath = 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: '', stroke: '', scaleX: 0.2, scaleY: 0.2 });
    canvas.add(oImg);
    canvas.renderAll();
});
#c {
    border:1px solid #ccc;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
<button onClick='toSvg();'>TOSVG</button>
<canvas id="c" width="400" height="400"></canvas>
<div id="svg"></div>

Upvotes: 2

Related Questions