2

I have an image editor that I'm trying to add external images to.

When I add the crossOrigin property to the img object it fails to load the image on the canvas and I get an error in the console "Error loading https://i.ytimg.com/vi/JphpLkmimVo/hqdefault.jpg". If I remove the crossOrigin it allows the image to be added but then when I export the canvas as an image I get a security error. I've read that adding it without the crossOrigin taints the canvas. Can anyone tell me why I can just keep the crossOrigin property?

var stockImageSrc = 'https://i.ytimg.com/vi/JphpLkmimVo/hqdefault.jpg';
fabric.Image.fromURL(stockImageSrc, function (oImg) {
                        oImg.setWidth(640);
                        oImg.setHeight(390);
                        canvas.add(oImg); 
                        canvas.renderAll();
                    }, { crossOrigin: '' });

Here is a demo

NullReference
  • 4,404
  • 12
  • 53
  • 90
  • 1
    The remote site needs to allow you to use their images in that way on your domain – _they_ have to do that, it is nothing _you_ can decide. – CBroe Jun 24 '15 at 22:25
  • @CBroe As far as I can tell these images, YT thumbnails, are returned from their API and designed to be used on different domains. Is there a way to tell if they're not allowing that? – NullReference Jun 24 '15 at 22:33
  • 1
    That example URL you have shown does not send any CORS headers. So no, it doesn’t seem to be designed to be used in that way. I’d suggest you go look into their documentation/FAQ to see if they mention CORS anywhere. – CBroe Jun 24 '15 at 22:35

5 Answers5

15

In my experience, you have to set the crossOrigin to Anonymous. In an example:

var stockImageSrc = 'https://i.ytimg.com/vi/JphpLkmimVo/hqdefault.jpg';
fabric.Image.fromURL(stockImageSrc, function (oImg) {
    ... other code here...
 }, { crossOrigin: 'Anonymous' });

This comes directly from the Mozilla developer documentation.

HOWEVER, you'll also see on that documentation that this is not supported by all browsers - namely Internet Explorer. I've found that it does NOT work in IE10 and older - IE11 does work. I am using a workaround for this for IE10 and older - see below. The reason this does not work is that older versions of IE do not have the full CORS ruleset implemented as well as a lack of full HTML5 (which Canvas is a part of) added to it's codebase. Firefox and Webkit browsers however (Chrome, Safari, etc.) are always updating and thus they are far ahead of the IE counterparts in these newer standards.

The other piece to this however is that the server hosting the image does need to have the Access-Control-Allow-Origin header set to either the domain of the page that is making the request or to *. This comes from the Mozilla CORS documentation. As @CBroe mentioned above, the youtube image you reference does NOT have have that header set. Therefore this image will taint your canvas.

The way to work around the crossOrigin for IE10 and older, and I'm guessing this workaround might work for images that do not have the Access-Control-Allow-Origin header set, is to use a server-side proxy. The concept here is that you send a request for the YouTube image to a server on the same domain that your webpage is hosted from. This keeps all image requests on the same domain, thus passing CORS rules. But the YouTube image of course isn't hosted on that server. The script that responds on your server would then request the YouTube server (or any other for that matter), capture the image on the server, and then pass it back to the browser. This can be done via a CURL request or some other methods. Make sure to set the Access-Controls-Allow-Origin header from the proxy server. You can find more information on a CORS Server Proxy here.

All that said, sadly there isn't a quick/simple answer to your question. Browsers are trying to offer it's users top security, and cross site scripting can create some issues that can steal identities, etc. That's the reason for the extra hoops to jump through.

Also of note, you could look at JSONP solutions (an alternative to CORS), however it's an older standard and has some negatives - I believe it's not fully supported in some of the more modern browsers and has some security concerns. Do a quick Google search to find out more on that option.

PromInc
  • 1,174
  • 7
  • 12
  • 1
    I am using fabric 2.x and to achieve this you need to slightly modify the Image object declaration. fabric.Image.fromURL(ImageSrc, callback, null, { crossOrigin: 'Anonymous' }) – nilay jha May 21 '18 at 09:30
  • Can we load video on fabric canvas? – Umesh Patadiya Jan 30 '20 at 12:32
  • Not sure, never tried. And sadly haven't worked with canvas in a few years now... A Google search implies it is possible though. – PromInc Jan 31 '20 at 13:41
  • Hi @PromInc: I have same issue even though i also applied `crossOrigin: 'Anonymous'`. Still i am facing the same error as: Access to image at 'localhost:9000/assets/images/image.png' from origin 'localhost:9001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Ref: https://jsfiddle.net/jasie/r3fqu4p6/33/ – TMA Mar 06 '20 at 10:46
  • This solution actually does save the day in one use case: When a user chooses a local file to upload into your canvas, and you want to get an image snapshot of the canvas after they've done edits. In this case, there is no server (it's the client's machine uploading into the clients' session of the app). However, when trying to do the same for 3rd-party resources (ie an external website) this doesn't solve CORS issues. Wrestle as much as you want, the silver bullet solution will always be to make a proxy server. Comes with numerous benefits, like protecting API keys. – Kalnode Feb 16 '21 at 23:16
  • Also: Loading external SVG's has extra scrutiny, compared to images. – Kalnode Feb 16 '21 at 23:17
  • This's the exact right solution for the mentioned problem, Thanks so much – Ahmed Sayed Sk Jul 10 '23 at 03:37
1

Try to add the image like this:

                var imgObj = new Image();

                imgObj.crossOrigin = "Anonymous";
                imgObj.src = http://example.com/50/50 ; //the source of your image

                imgObj.onload = function () {
                    // start fabricJS stuff
                    imgObj.width = 100
                    imgObj.height = 100

                    var image = new fabric.Image(imgObj);
                    image.set({
                        top: 100,
                        left: 100 ,
                        padding: 10,
                        cornersize: 10,
                    });

                    //image.scale(0.1).setCoords();
                    canvas.add(image);
                }
Rus Mine
  • 1,523
  • 1
  • 18
  • 29
0

You can try that way(ofcourse thats only a proof of concept)

function getCORSImage(url) {
  var oIm=document.createElement("img");
  oIm.setAttribute('src', url);
  return new fabric.Image(oIm,{
    width: 640
    height: 390
  });
}
Marcus
  • 341
  • 1
  • 8
0

Tried all above solutions but but didn't worked in my case. i was using AWS s3
image URL. Work well in local environment but didn't deploy in cloud. So this works for me.

const toDataUrl = (url, callback) => {
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
        var reader = new FileReader();
        reader.onloadend = function () {
            callback(reader.result);
        }
        reader.readAsDataURL(xhr.response);
    };
    xhr.open('GET', url);
    xhr.responseType = 'blob';
    xhr.send();
}

//it cal be on load OR call it manually
useEffect(() => {

    //s3 image url 
    let testimonial_bg = 'https://s3.amazonaws.com/bucketname/foldername/imagename.jpg';

    toDataUrl(`${testimonial_bg}?time=${Date.now()}`, (myBase64) => {
            var img = new Image();
            img.onload = () => {
                let loadedImage = new fabric.Image(img, {
                    selectable: false,
                    id: 'mainbg',
                    left: 0,
                    top: 0,
                    crossOrigin: 'Anonymous'
                });
                loadedImage.scaleToHeight(canvasWidth);
                loadedImage.scaleToWidth(canvasWidth);
                canvas.current.setBackgroundImage(loadedImage);
                canvas.current.renderAll();
            }
            img.src = myBase64;
        });
}, []);

Hope it helps. :)

Anis Momin
  • 11
  • 2
-1

After searching for 1 day i found this

Instead of fabric.Image.fromURL

fabric.util.loadImage worked for me

like over here

vijay
  • 10,276
  • 11
  • 64
  • 79
  • fabric.Image.fromURL works just fine for CORS. I haven't upgraded in a while, but this should work: `fabric.Image.fromURL(imageUrl, callbackFunction, {crossOrigin: 'anonymous'});` – Albin Aug 24 '17 at 20:28
  • In future, you might want to back up your answers with reason and logic, avoid "DON'T DO THIS" answers if you're giving a properly structured answer you should state why not, with evidence as to why it shouldn't be done that way. With this answer being a while ago I hope you've learned to provide better answers. – Llanilek Mar 06 '19 at 13:28