fengyuanchen / cropperjs

JavaScript image cropper.
https://fengyuanchen.github.io/cropperjs/
MIT License
13.12k stars 2.42k forks source link

Failed to execute 'toDataURL' Tainted canvases may not be exported #258

Closed mehdidarmiche closed 6 years ago

mehdidarmiche commented 6 years ago

I'm trying to crop an image from a link, it works for the first time but when i reload the page and i try to crop the same image i got ( Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported. ) i use amazon s3 for storage and i configure * as CORS Rule . Thanks a lot.

fengyuanchen commented 6 years ago

Try to adding crossOrigin attribute to the image:

<img src="path/to/s3.jpg" crossorigin="anonymous">

Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin

robinson90 commented 6 years ago

i tried your method ,but it does not work .here is my codes :

<div id="demo" style="display:inline-block;">
  测试pc页面生成图片M<br>
  <img src="https://ss0.baidu.com/73x1bjeh1BF3odCf/it/u=138126325,1485620701&fm=85&s=7FAB2EC3909A35D01E299C1A030010D2" crossorigin>
</div>
let demo = $("#demo") ;
let $canvas = document.createElement("canvas") ;
let w = demo.width();
let h = demo.height();
let scaleBy = 2;
$canvas.width = w*scaleBy;
$canvas.height = h*scaleBy;
$canvas.style.width = w+"px" ;
$canvas.style.hight = h+"px";
var context = $canvas.getContext("2d");
context.scale(scaleBy,scaleBy);

html2canvas(demo,{ 
          allowTaint: true,
          canvas: $canvas,
        onrendered:function(canvas){
              dataURL = canvas.toDataURL("image/png");
             $("body").append(canvas);

          },
          width:w,
          height:h
});
robinson90 commented 6 years ago

@fengyuanchen my codepen demo address: https://codepen.io/robinson90/pen/LryZEV

martianmartian commented 6 years ago

doesn't work on mobile devices

bartkim0426 commented 6 years ago

@robinson90

you're codepen demo seems to worked well. How did you solve the problem?

fengyuanchen commented 6 years ago

The target image server also needs to set the Response Header: Access-Control-Allow-Origin: *.

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

bhinderbaljeet commented 6 years ago

I tried every possible solution given on the internet related to this. But In the end I had to fix this by a dirty patch. For those like me who are still struggling to get this done, can try folowing:

Note: My use case was to load an s3 url into my cropper, It might be similar for others as well

  1. Update the URL of s3 to start with http from https
  2. Added bucket configurations as follow:
    <?xml version="1.0" encoding="UTF-8"?>
    <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    </CORSRule>
    </CORSConfiguration>
    <!-- Basically tell your server to allow access-control-allow-origin:* -->
  3. Initialize cropper
    this.cropper = new Cropper(document.getElementById('cropper'), {
    checkCrossOrigin:false, // This is important
    crop(event) {
      setTimeout(()=>{
         /**---------------------------------------------------------------------
           In order for crossorigin to work we need to add crossorigin attribute 
           to the dynamically created img element, (See Attachment)
         ---------------------------------------------------------------------*/
         let canvas_img = window.document.querySelector('img.cropper-hide');
         let src = canvas_img.getAttribute('src');
         canvas_img.setAttribute('crossorigin', 'anonymous')
         /*Somehow src needs to be set again in order for crossorigin to work */
         canvas_img.setAttribute('src', src)
      },100)        
    }
    });

    I hope this helps someone.

cropperjs

sshgun commented 6 years ago

I tried every possible solution given on the internet related to this. But In the end I had to fix this by a dirty patch. For those like me who are still struggling to get this done, can try folowing:

Note: My use case was to load an s3 url into my cropper, It might be similar for others as well

  1. Update the URL of s3 to start with http from https
  2. Added bucket configurations as follow:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>
<!-- Basically tell your server to allow access-control-allow-origin:* -->
  1. Initialize cropper
this.cropper = new Cropper(document.getElementById('cropper'), {
    checkCrossOrigin:false, // This is important
    crop(event) {
      setTimeout(()=>{
         /**---------------------------------------------------------------------
           In order for crossorigin to work we need to add crossorigin attribute 
           to the dynamically created img element, (See Attachment)
         ---------------------------------------------------------------------*/
         let canvas_img = window.document.querySelector('img.cropper-hide');
         let src = canvas_img.getAttribute('src');
         canvas_img.setAttribute('crossorigin', 'anonymous')
         /*Somehow src needs to be set again in order for crossorigin to work */
         canvas_img.setAttribute('src', src)
      },100)        
    }
});

I hope this helps someone.

cropperjs

you're the best!

@baljeetbhinderI think that cropperjs must check these attributes and added if there exist in the initial image.

patricksebastien commented 6 years ago
  1. Update the URL of s3 to start with http from https

@baljeetbhinder Is this absolutely necessary ^ - I think it will cause a warning when loading a image in http from a https website (Mixed Content)?

bhinderbaljeet commented 6 years ago

@patricksebastien you made a good point, I just realised that when I wrote my comment above, The task was under development environment which was not having ssl and that is why I had to shift to http to preview it correctly. I did not change the https part in production environment and yet it is working fine there. Thanks for reporting that.

mamzellejuu commented 5 years ago

thanks @baljeetbhinder help me to resolve the problem that I add on my project with canvas!

schnmudgal commented 5 years ago

Thanks @baljeetbhinder. You ended my 12 hours struggle. You may improve it by changing your code be triggered on ready and not on crop maybe. Sometimes you don't crop while initialising, like autoCrop: false. Then crop() method won't be triggered at all. Also, you will not need to put setTimeout if you add the code in ready as it already takes care of asynchronous thing. But thanks again 🙂

bhinderbaljeet commented 5 years ago

@schnmudgal Makes sense!! Thank You for updating my knowledge

ryanzhou7 commented 4 years ago

I tried every possible solution given on the internet related to this. But In the end I had to fix this by a dirty patch. For those like me who are still struggling to get this done, can try folowing:

Note: My use case was to load an s3 url into my cropper, It might be similar for others as well

  1. Update the URL of s3 to start with http from https
  2. Added bucket configurations as follow:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>
<!-- Basically tell your server to allow access-control-allow-origin:* -->
  1. Initialize cropper
this.cropper = new Cropper(document.getElementById('cropper'), {
    checkCrossOrigin:false, // This is important
    crop(event) {
      setTimeout(()=>{
         /**---------------------------------------------------------------------
           In order for crossorigin to work we need to add crossorigin attribute 
           to the dynamically created img element, (See Attachment)
         ---------------------------------------------------------------------*/
         let canvas_img = window.document.querySelector('img.cropper-hide');
         let src = canvas_img.getAttribute('src');
         canvas_img.setAttribute('crossorigin', 'anonymous')
         /*Somehow src needs to be set again in order for crossorigin to work */
         canvas_img.setAttribute('src', src)
      },100)        
    }
});

I hope this helps someone.

cropperjs

If you do have to support IE11 note that this newer ES6 method definition crop(event) { is not supported. Instead use the old fashioned crop: function(event) {

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions

budongbai commented 3 years ago

I tried every possible solution given on the internet related to this. But In the end I had to fix this by a dirty patch. For those like me who are still struggling to get this done, can try folowing:

Note: My use case was to load an s3 url into my cropper, It might be similar for others as well

  1. Update the URL of s3 to start with http from https
  2. Added bucket configurations as follow:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>
<!-- Basically tell your server to allow access-control-allow-origin:* -->
  1. Initialize cropper
this.cropper = new Cropper(document.getElementById('cropper'), {
    checkCrossOrigin:false, // This is important
    crop(event) {
      setTimeout(()=>{
         /**---------------------------------------------------------------------
           In order for crossorigin to work we need to add crossorigin attribute 
           to the dynamically created img element, (See Attachment)
         ---------------------------------------------------------------------*/
         let canvas_img = window.document.querySelector('img.cropper-hide');
         let src = canvas_img.getAttribute('src');
         canvas_img.setAttribute('crossorigin', 'anonymous')
         /*Somehow src needs to be set again in order for crossorigin to work */
         canvas_img.setAttribute('src', src)
      },100)        
    }
});

I hope this helps someone.

cropperjs

image I tried it as what commented by you, but got another exception. InvalidStateError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state. getSourceCanvas node_modules/cropperjs/dist/cropper.js:1013 1010 | context.scale(scaleX, scaleY); 1011 | context.imageSmoothingEnabled = imageSmoothingEnabled; 1012 | context.imageSmoothingQuality = imageSmoothingQuality; 1013 | context.drawImage.apply(context, [image].concat(_toConsumableArray(params.map(function (param) { 1014 | return Math.floor(normalizeDecimalNumber(param)); 1015 | })))); 1016 | context.restore();

developdeez commented 3 years ago

Hey. Is there a React equivalent to this fix?

zubin-madon commented 2 years ago

@baljeetbhinder I have a similar issue in my react canvas app. I am using images from http://traits.s3.filebase.com/...png on my React-Canvas. But it won't allow me to save them.

function saveImage() {
        let imageToSave = new Image();
        imageToSave.crossOrigin="anonymous"
        imageToSave.src = hiddenCanvas.current.toDataURL('image/png', 1.0);
        setSavedImage(imageToSave.src)
    }

Trying to display <img src={savedImage} alt="test result" crossOrigin></img> I added that crossOrigin line. But no success. What else is needed?

zubin-madon commented 2 years ago

@baljeetbhinder I also added the following CORS json to the server hosting my images:

{
    "CORSRules": [
        {
            "AllowedHeaders": [],
            "AllowedMethods": [
                "GET"
            ],
            "AllowedOrigins": [
                "*"
            ],
            "ExposeHeaders": []
        }
    ]
}
zubin-madon commented 2 years ago

@baljeetbhinder adding CORS and the anon tags in react hasnt worked for me still! Someone plz answer my SO query here: https://t.co/jBXmxvn3Ao

xgqfrms commented 1 year ago

Try to adding crossOrigin attribute to the image:

<img src="path/to/s3.jpg" crossorigin>
- <img src="path/to/s3.jpg" crossorigin>
+ <img src="path/to/s3.jpg" crossorigin="anonymous">

test

situation: using js auto download image with canvas API

function autoDownloadImageWithCanvas(src = "https://cdn.xgqfrms.xyz/logo/icon.png") {
  const canvas = document.createElement("canvas");
  canvas.width = 300;
  canvas.height = 300;
  const ctx = canvas.getContext("2d");
  const img = document.createElement(`img`);
  // fix: Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
  // img.crossorigin = "anonymous"; // ❌
  img.setAttribute('crossorigin', 'anonymous'); // ✅
  img.src = src;
  img.onload = function() {
    ctx.drawImage(img, 0, 0);
    setTimeout(() => {
      const dataURL = canvas.toDataURL();
      console.log(`dataURL =`,  dataURL);
      const a =  document.createElement(`a`);
      a.href = dataURL;
      a.download = src.slice(src.lastIndexOf(`/`) + 1);
      a.click();
    }, 0);
  }
}

autoDownloadImageWithCanvas();

refs

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-crossorigin:~:text=to%20a%20bookmark.-,crossorigin,-Indicates%20if%20the

https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image#security_and_tainted_canvases

fengyuanchen commented 1 year ago

@xgqfrms crossorigin = crossorigin="" = crossorigin="anonymous".

Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin

xgqfrms commented 1 year ago

@fengyuanchen

You're right, but I think it's best to make explicit statements to avoid unnecessary misunderstandings.

image

And, crossorigin isn't a boolean attribute of the HTML5 specification.

https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes

fengyuanchen commented 1 year ago

@xgqfrms All right, I will update the original/first answer.

Ritik-Banger-Biz4group commented 1 year ago

Have you guys found any relevant react solution for this? I have added the cross-origin thing but still facing the same error. @developdeez