fabricjsfabricjs2

FabricJS - "Tainted canvases may not be exported" JS Error when run toDataURL


I have a custom component called ImageContainer, which basically creates a rectangle and applies an image as a fill pattern.

When I run function toDataURL on the canvas, I get a js error

Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

The result I trying to achieve should create a png data and apply it on image tag as a src.

Any idea how could it be fixed? Tnx

https://jsfiddle.net/redlive/a444p13x/


Solution

  • You need to add crossOrigin: 'anonymous' to the image element. Have added in fabric.Image.fromURL, or you need to use images from same server, or where crossOrigin is defined.

    DEMO

    fabric.ImageContainer = fabric.util.createClass(fabric.Rect, {
      type: 'image-container',
      initialize: function(options) {
    	  options || (options = { });
        options.content || (options.content = { });
        options.content.angle = options.content.angle || 0;
        
        this.callSuper('initialize', options);
        this.set({
          objectCaching: false,
          ddpPreviousCenter: this.getCenterPoint()
        });
        
        this.on('scaling', function(el){
          this.set({
    	      width: this.width * this.scaleX,
            height: this.height * this.scaleY,
            scaleX: 1,
            scaleY: 1
          });
          this.setCoords();
          this._drawImage();
        }.bind(this));
        
        this.on('modified', function(el){
    				this._updateContentCoords();
        }.bind(this));
      
        this._drawImage();        
      },
      _updateContentCoords: function(){
      		const ddpPreviousCenter = {...this.ddpPreviousCenter};
          const content = {...this.content};
          const shiftX = this.getCenterPoint().x - ddpPreviousCenter.x;
          const shiftY = this.getCenterPoint().y - ddpPreviousCenter.y;
    
          content.left += shiftX;
          content.top += shiftY;
        
          this.set({
            ddpPreviousCenter: this.getCenterPoint(),
            content
          });
      },
      _drawImage: function() {
        const scaleFactor = 1;
        const imgSrc = [
          'https://picsum.photos/',
          this.content.width * scaleFactor,
          '/',
          this.content.height * scaleFactor
        ].join('');
    
        fabric.Image.fromURL(imgSrc, function(img) {
          img.set({
            left: this.content.left - this.left + this.content.width / 2,
            top:  this.content.top - this.top + this.content.height / 2,
            scaleX: 1,
            scalY: 1,
            angle: this.content.angle,
            originX: 'center',
            originY: 'center',
          });
          //            img.scaleToWidth(this.content.width);
          const patternSourceCanvas = new fabric.StaticCanvas();
          patternSourceCanvas.setDimensions({
            width: this.width,
            height: this.height
          });
    
         	patternSourceCanvas.setBackgroundColor(this.backgroundColor);
          patternSourceCanvas.add(img);
          patternSourceCanvas.renderAll();
    
          const pattern = new fabric.Pattern({
          	source: function() {
              return patternSourceCanvas.getElement();
            },
            repeat: 'no-repeat'
          });
          
          this.set({
            fill: pattern
          });
    
          this.canvas.renderAll();
          this.canvas.fire('image:pattern:loaded');
          
        }.bind(this),{
          crossOrigin: 'anonymous'
        });
      },
      toObject: function(options) {
            return fabric.util.object.extend(this.callSuper('toObject'), {
                ddpPreviousCenter: this.get('ddpPreviousCenter'),
                content: this.get('content'),
            });
    
            // return fabric.util.object.extend(this.callSuper('toObject'), {});
        },
      fromObject: function(object, callback) {
      	return fabric.Object._fromObject('ImageContainer', object, callback);
    	},
      _render: function(ctx) {
        this.callSuper('_render', ctx);
      }
    });
    
    fabric.ImageContainer.__fromObject = function(object, callback, forceAsync) {
        if (!forceAsync) {
          fabric.util.enlivenPatterns([object.fill, object.stroke], function(patterns) {
          console.log(patterns);
            object.fill = patterns[0];
            object.stroke = patterns[1];
            var rect = new fabric.ImageContainer(object);
            callback && callback(rect);
          });
        }
        else {
          var rect = new fabric.ImageContainer(object);
          callback && callback(rect);
          return rect;
        }
      };
    
    // =========================================================================
    
    let store;
    const canvas = new fabric.Canvas('paper');
    
    const container = new fabric.ImageContainer({
      left: 10,
      top: 10,
      width: 150,
      height: 150,
      backgroundColor: 'green',
      content: {
        left: 20,
        top: 20,
        width: 130,
        height: 130
      }
    });
    
    canvas.on('image:pattern:loaded', function(){
      $('#img').attr('src', this.toDataURL());
    });
    
    canvas.add(container);
    canvas.renderAll();
    #paper {
      border: solid 1px red;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.js"></script>
    <img id="image" />
    <canvas id="paper" width="400" height="200" style="border:1px solid #ccc"></canvas>
    <img id="img" />