Serks
Serks

Reputation: 333

Fabric.js: Use SVG as a pattern & adjust fill of SVG pattern

I am using fabric.js with Meteor.

I have the following SVG image which I am using as a pattern on a path within a path group.

enter image description here

As you can see there is some transparency to it.

What I wish to do is to fill a path with this pattern, and then use a color picker to be able to manipulate it's colour. I have been able to do this so far by adding the SVG as a pattern, and then duplicating the path and filling that with a colour with 50% opacity, but of course this changes the whole area colour, not just the patterns colour. Basically since the pattern is already a 'fill' of a path, how would I fill the pattern itself?

Here's a diagram to better explain...

enter image description here

Here's the code I am using to add the pattern when a particular pattern is selected from a dropdown list (using Meteor event)...

'change .shapeTopPattern': function(e, template){ e.preventDefault();

      var textureID = e.target.value;

      if(textureID != '') { //If this shape has a texture selected.
          var texture = Textures.findOne(textureID);

          //Get the image
          var textureIMG = new Image;
          textureIMG.crossOrigin = "anonymous";
          textureIMG.src = texture.image;

          var patternURL = textureIMG.src;
          fabric.Image.fromURL(patternURL, function(img) {

              var obj = canvas.getActiveObject();
              var paths = obj.paths;

              paths.forEach(function(p) {
                if (p.pathName == "shapeTopTexture") {
                    img.width = p.width;
                    img.height = p.height

                    var patternSourceCanvas = new fabric.StaticCanvas();
                    patternSourceCanvas.add(img);
                    patternSourceCanvas.setDimensions({
                        width: p.width,
                        height: p.height
                      });
                    var texture = patternSourceCanvas.getElement();

                    var pattern = new fabric.Pattern({
                      source: texture,
                      repeat: 'no-repeat',
                      offsetX: p.width/2,
                      offsetY: p.height/2
                    });

                    obj.setFill();

                    if (obj instanceof fabric.PathGroup) {
                        p.fill = pattern;
                        obj.perPixelTargetFind = false;
                    } else {
                        obj.setFill(pattern);
                    }
                    canvas.renderAll();
                }
              });
           });

      }
  },

This is the function I am using on a change event of the color picker. "shapeTopPatternColour" is the duplicated path (top of the cylinder) from the cylinder path-group. I know that this is obviously why the colour is going over the entire area instead of only the pattern, but it's the closest I got to what I wanted.

changeTopPatternColour = function(o, color, opacity) {
    /*
    * o = object
    * color = rgbcolor data
    * opacity = .5
    */
    var paths = o.paths;
    paths.forEach(function(p) {
      if (p.pathName == "shapeTopPatternColour") {
        p.setFill(color);
        p.opacity = opacity;
      }
    });
    canvas.renderAll();
};

So how can I...

Thanks in advance.

Upvotes: 2

Views: 4100

Answers (1)

invernomuto
invernomuto

Reputation: 10211

Unfortunately I could not find a way to use as a pattern an svg file without having necessarily to rasterize it, I am sorry. In the example below you can see for yourself that I always entirely rewrite the surfaces. Instead of loading the svg like an image, I load the svg normally and then I add it to the canvas using a pathgroup. For this snippet I am using your files as strings to avoid annoying CORS issues.

Here you can find the bitbucket repository

All the best.

$(function() {
  var canvas = new fabric.Canvas('c');
  canvas.setWidth(600);
  canvas.setHeight(370);
  var tint = function(color) {
    canvas.clear();
    var svgObjectString = '<?xml version="1.0" encoding="utf-8"?>' +
      '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"' +
      ' viewBox="0 0 596 366" style="enable-background:new 0 0 596 366;" xml:space="preserve">' +
      '<style type="text/css">' +
      '.st0{fill:#E0E0E0;}' +
      ' .st1{fill:url(#SVGID_1_);}' +
      '</style>' +
      '<path class="st0" d="M596,103.5c0,32-41.7,60.5-107.2,79.5c-51.7,15-118.2,24-190.8,24s-139.1-9-190.8-24C41.7,164,0,135.4,0,103.5' +
      'c0-0.2,0-0.3,0-0.5C0.8,46.1,133.9,0,298,0s297.2,46.1,298,103C596,103.2,596,103.3,596,103.5z"/>' +
      '<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0" y1="133.25" x2="596" y2="133.25" gradientTransform="matrix(1 0 0 -1 0 368)">' +
      '<stop  offset="0" style="stop-color:#E8E8E8"/>' +
      '<stop  offset="0.23" style="stop-color:#878787"/>' +
      '<stop  offset="0.6" style="stop-color:#D6D6D6"/>' +
      '<stop  offset="1" style="stop-color:#ADADAD"/>' +
      '</linearGradient>' +
      '<path class="st1" d="M596,103.5v159c0,0.2,0,0.3,0,0.5c-0.8,56.9-133.9,103-298,103S0.8,319.9,0,263c0-0.2,0-0.3,0-0.5v-159' +
      'c0,32,41.7,60.5,107.2,79.5c51.7,15,118.2,24,190.8,24s139.1-9,190.8-24C554.3,164,596,135.4,596,103.5z"/>' +
      '</svg>';
    fabric.loadSVGFromString(svgObjectString, function(svgobject) {
      //fabric.loadSVGFromURL('../../Content/object.svg', function (svgobject) {
      var objGroup = new fabric.PathGroup(svgobject, {
        width: 596,
        height: 500
      });
      canvas.add(objGroup);

      var svgPatternString = '<?xml version="1.0" encoding="utf-8"?>' +
        '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' +
        '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"' +
        ' width="576px" height="576px" viewBox="0 0 576 576" enable-background="new 0 0 576 576" xml:space="preserve">' +
        '<polygon fill="#848484" points="290.664,286.625 244.214,729.625 338.422,729.625 "/>' +
        '<polygon fill="#848484" points="290.711,286.625 337.16,-156.375 242.953,-156.375 "/>' +
        '<polygon fill="#848484" points="290.688,286.602 -152.312,240.152 -152.312,334.359 "/>' +
        '<polygon fill="#848484" points="290.688,286.648 733.688,333.098 733.688,238.89 "/>' +
        '<polygon fill="#848484" points="290.664,286.625 244.214,729.625 338.422,729.625 "/>' +
        '<polygon fill="#848484" points="290.711,286.625 337.16,-156.375 242.953,-156.375 "/>' +
        '<polygon fill="#848484" points="290.688,286.602 -152.312,240.152 -152.312,334.359 "/>' +
        '<polygon fill="#848484" points="290.688,286.648 733.688,333.098 733.688,238.89 "/>' +
        '<polygon fill="#848484" points="290.666,286.616 75.532,676.646 162.318,713.297 "/>' +
        '<polygon fill="#848484" points="290.709,286.634 505.843,-103.396 419.057,-140.047 "/>' +
        '<polygon fill="#848484" points="290.696,286.604 -99.334,71.469 -135.984,158.255 "/>' +
        '<polygon fill="#848484" points="290.679,286.646 680.709,501.78 717.359,414.994 "/>' +
        '<polygon fill="#848484" points="290.671,286.608 -59.646,561.716 5.948,629.336 "/>' +
        '<polygon fill="#848484" points="290.704,286.642 641.021,11.534 575.427,-56.086 "/>' +
        '<polygon fill="#848484" points="290.704,286.609 15.597,-63.709 -52.024,1.885 "/>' +
        '<polygon fill="#848484" points="290.671,286.641 565.778,636.958 633.398,571.364 "/>' +
        '<polygon fill="#848484" points="290.679,286.603 -137.942,407.811 -102.993,495.296 "/>' +
        '<polygon fill="#848484" points="290.696,286.647 719.315,165.439 684.367,77.954 "/>' +
        '<polygon fill="#848484" points="290.709,286.617 169.502,-142.004 82.016,-107.055 "/>' +
        '<polygon fill="#848484" points="290.666,286.634 411.873,715.253 499.358,680.305 "/>' +
        '</svg>';

      fabric.loadSVGFromString(svgPatternString, function(svgpattern) {
        //fabric.loadSVGFromURL('../../Content/pattern.svg',
        //    function (svgpattern) {
        svgpattern.map(function(item) {
          item.fill = color;
        });
        var ptnGroup = new fabric.PathGroup(svgpattern, {
          top: -190,
          width: 596,
          height: 500
        });
        var paths = objGroup.paths;
        var path = paths[0];
        svgpattern.width = path.width;
        svgpattern.height = path.height;
        var patternSourceCanvas = new fabric.StaticCanvas();
        patternSourceCanvas.add(ptnGroup);
        patternSourceCanvas.setDimensions({
          width: path.width,
          height: path.height
        });
        var texture = patternSourceCanvas.getElement();
        var pattern = new fabric.Pattern({
          source: texture,
          repeat: 'no-repeat',
          offsetX: path.width / 2,
          offsetY: path.height / 2
        });
        objGroup.setFill();
        objGroup.globalCompositeOperation = "source-over";
        path.setFill("#ff0000");
        path.fill = pattern;
        objGroup.perPixelTargetFind = false;
        canvas.renderAll();
      });
    });
  }
  $('button')
    .on('click',
      function(e, args) {
        tint(e.target.value);
      });
});
canvas {
  border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.2/fabric.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
  <div class="row">
    <div class="col-md-7">
      <canvas id="c" style="border: solid 1px #ccc;"></canvas>
    </div>
    <div class="col-md-5">
      <button class="btn-danger" value="red">red</button>
      <button class="btn-success" value="green">green</button>
      <button class="btn-primary" value="blue">blue</button>
    </div>
  </div>
</div>

Edit: to retrieve the dimensions of the top of your cylinder, I am using the first path of svgobject

var paths = objGroup.paths;
var path = paths[0];
svgpattern.width = path.width;
svgpattern.height = path.height;
var patternSourceCanvas = new fabric.StaticCanvas();
patternSourceCanvas.add(ptnGroup);
patternSourceCanvas.setDimensions({
  width: path.width,
  height: path.height
});

Upvotes: 3

Related Questions