7

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/

Eugene Vilder
  • 492
  • 1
  • 9
  • 26

4 Answers4

12

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" />
Durga
  • 15,263
  • 2
  • 28
  • 52
2

I had to clear cache after adding crossOrigin: 'anonymous'.

Azhar
  • 145
  • 1
  • 3
  • 10
0

Here is how I solved this issue. The issue is because you cannot have an external image URL inside of your canvas. You must fetch the image and create a local copy of it. In my example I am setting the background of the canvas to the image but you can do the same for adding images to the canvas.

  fetch("https://i.imgur.com/fHyEMsl.jpg")
    .then(result => result.blob())
    .then(blob => {
        const url = URL.createObjectURL(blob);
        fabric.Image.fromURL(url, function (Image) {
            canvas.setBackgroundImage(Image);
            canvas.renderAll();
        })
    });
thattan
  • 56
  • 3
-3
fabric.Image.fromURL(imgSrc, (img) => {
  img._element.crossOrigin = 'anonymous'
  canvas.add(img).requestRenderAll()
})
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
Iliya
  • 1
  • 3
    Please read [answer] and [edit] your answer to contain an explanation as to why this code would actually solve the problem at hand. Always remember that you're not only solving the problem, but are also educating the OP and any future readers of this post. Additionally, this appears to add nothing to [this answer](https://stackoverflow.com/a/50404952/5211833) which explains why this would work and is already over 4 years old. – Adriaan Oct 04 '22 at 13:50