4

I have this code for create canvas image from different server urls

function getBase64Image(imageUri) { 
    var canvas = document.createElement("canvas");
    ctx = canvas.getContext("2d"); 
    var img = new Image();
    img.src = imageUri;
    img.crossOrigin = "Anonymous";
    img.onload = function() {
       canvas.width = this.width;
       canvas.height = this.height;
       ctx.drawImage(img, 0, 0, this.width, this.height);
       var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
       var dataURL = canvas.toDataURL("image/png");
       document.getElementById("dummyhiddenField").value = dataURL;
   };
}

Where I am trying to get image from server and set it's canvas url to hidden field but its working good with local image only

As per the other answers from stackoverflow i set crossOrigin = "Anonymous" which isn't working also not able to set access origin from server, Have to manage all the things from localscript

Thanks

Kirankumar Dafda
  • 2,354
  • 5
  • 29
  • 56
  • "As per the other answers from stackoverflow" — What other answers? – Quentin Jan 12 '16 at 12:47
  • "i set crossOrigin = "Anonymous" which isn't working" — I wouldn't have expected it to. It just means that when a CORS request is made, cookies and such won't be included. – Quentin Jan 12 '16 at 12:47
  • "not able to set access origin from server" — JavaScript can't read data from other origins without permission from that origin. That's a fundamental security feature. – Quentin Jan 12 '16 at 12:48
  • Thanks for the reply, I have working on this from last 8 to 10 hours to find out the solution where many stackoverflow users were pass out from this problem like below questions – Kirankumar Dafda Jan 12 '16 at 12:55
  • http://stackoverflow.com/questions/7988202/how-to-use-canvas-to-modify-images-from-another-domain http://stackoverflow.com/questions/25753754/canvas-todataurl-security-error-the-operation-is-insecure http://stackoverflow.com/questions/9972049/cross-origin-data-in-html5-canvas – Kirankumar Dafda Jan 12 '16 at 12:55
  • The first of those has three answers which say "You can't do it". The second one has one answer which says "The server must supply CORS headers", one which says "You can't" and one which appears to predate the cross-origin stuff being added to the question. The third of those has one answer which says "Turn off security in your browser", one answer (with a surprising number of upvotes) which sets an invalid value to `crossOrigin` and then accesses a **same** origin URL, and one which says you need to use CORS on the server hosting the image. In short: You need to use CORS on the image server. – Quentin Jan 12 '16 at 13:00
  • I have webservices only to do any kind of changes in images and I am creating an app for to run locally, so is there any direct solution available to work in my case ? then please suggest me. Thank you so much – Kirankumar Dafda Jan 12 '16 at 13:06
  • You need to use CORS on the image server – Quentin Jan 12 '16 at 13:06
  • Or store the images locally – Canvas Jan 12 '16 at 13:06
  • Yes, @Canvas, i was thinking of set images locally, but my app is working online and offline too, so if there is internet connection available the app will fetch data from server and if not then the data will set from the lastly loaded content from server to localStorage database. – Kirankumar Dafda Jan 12 '16 at 13:11
  • The only workaround if you don't have access to the original webservers, is to use a proxy server, which will fetch the data and render it without cross-origin if it's the same origin as your scripts, or with proper `Access-Control-Allow-Origin "*"` headers. But I hope that when you say "Have to manage all the things from localscript" you don't mean through the `file://` protocol, otherwise, I'm not even sure crossorigin will work... – Kaiido Jan 12 '16 at 13:38
  • So, I have to find another way or finally set the `Access-Control-Allow-Origin "*"` to the server from where my webservices located. – Kirankumar Dafda Jan 12 '16 at 13:42
  • @Quentin, for your second comment, no it won't work, if the browser does a request for a resource with the crossorigin attribute, and that the server doesn't allow the requested origin, the server will just answer there is a problem, and the browser will throw an error. Also, any non-valid value for crossorigin attribute is defaulted to `'anonymous'` (`'Anonymous'` included ;-) ) Ps : The only two valid values are `'anonymous'` and `'use-credentials'` – Kaiido Jan 12 '16 at 13:44
  • @Kaiido — Err. My second commend says that that won't work. You seem to be agreeing with me. – Quentin Jan 12 '16 at 13:50
  • @Quentin Then I misread your comment (thought you were expecting it to work) – Kaiido Jan 12 '16 at 13:52

1 Answers1

18

An updated look at Html5 Canvas with Cross-Domain content.

Here is an updated look at how cross-domain content affects the html5 canvas and how to work within the security restrictions that apply to cross-domain content

An update is useful today (2016-Jan) because there are several new(ish) methods to allow cross-domain images to be drawn to canvas without tainting the canvas.

Drawing cross-domain content on html5 canvas will cause it to be "tainted"

You can draw an image from another domain on the canvas and it will display on the canvas. Accessing resources from another domain is called "Cross Origin Resource Sharing" -- and is generally known as "CORS" for short.

Drawing CORS content (f.ex: images) will cause the canvas to be "tainted" for security reasons.

If the canvas is tainted you cannot use these canvas & context methods:

  • context.getImageData to fetch the pixel data on the canvas
  • canvas.toDataURL to export the canvas as an image

You can't "trick" canvas into violating its CORS security restrictions -- hopefully you wouldn't even want to try! But you can draw cross-origin images without tainting the canvas by satisfying CORS security restrictions.

The "usual" (and simplest) way of handling images so they don't taint the canvas:

Put your images in the same domain as the webpage. You can have several physical servers delivering content but the image domain must be the same as the html code (or the javascript code) that creates the canvas. CORS restrictions are satisfied and the canvas is not tainted.

Notes about CORS while you're developing on your own computer

Solution#1(!): You can install a web server on your dev computer and serve both the web page files (.html, .js, etc) and your image files (.png, .jpg, etc) from one domain.

The folders of your development computer are declared to be different domains. So drawing an image from a subdirectory on your local disk will violate CORS restrictions because different local folders are different domains.

Solution#2: While doing development, you can put both your webpage files and your image files on your desktop and the images will be declared to be in the same domain and your canvas will not be tainted.

Satisfying CORS restrictions when images are on different domains

Solution#3: You can use cross-domain images on canvas without tainting it. To do that, you must satisfy these requirements:

  • Clientside: The image object must have the crossOrigin attribute set to allow cross-origin content. This property can be set within the html element tag or within javascript. Enabling settings are "anonymous" and "use-credentials".

  • Serverside: The server must be configured to return header(s) indicating that the response contains authorized content.

More than 1 response header might be needed depending on configuration:

Access-Control-Allow-Origin will return either anonymous authorization (*) or will return specific authorization based on the request.

Access-Control-Allow-Credentials is required if authentication requires additional information (like cookies).

Access-Control-Expose-Headers gives the client access to additional response information.

Enabling cross-domain requests on the server can be complex, especially when serving content that is authorized based on client roles. For additional information about starting configuration, you can visit: http://enable-cors.org/index.html.

Using cross-domain image hosts that allow anonymous access to their images

Solution#4: Some public image hosts allow you to upload images which will be served to clients in a CORS compliant way. Several examples are: imgur and dropbox. Stackoverflow images are hosted on Imgur.

Here's an example of how to serve images on Dropbox.com in a CORS compliant way:

  1. Sign up for a Dropbox Account.
  2. You are given several default folders. Upload your images in the special "Public" folder. This is the folder that Dropbox gives CORS compliant anonymous access.
  3. Right-click on an image you want to serve and select "Copy public link". Your clipboard will have a link to your CORS compliant image.
  4. Load the image onto your page with the img tag or in javascript.

Here's example code to get a CORS compliant image object from Dropbox using javascript:

var img=new Image();
img.crossOrigin='anonymous';
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/sun.png";
img.onload=start;
function start(){
    context.drawImage(img,0,0);
    // The canvas is not tainted so 
    // the following both work without errors
    var url=canvas.toDataURL();
    var imageData=context.getImageData(0,0,100,100);
}

New(ish): Satisfy CORS by having the clientside user give consent

CORS security restrictions are meant to stop bad people from secretly taking your information while leaving you unaware.

Until recently, browsers relied on client-server configuration to satisfy security requirements. Recently, browsers have begun allowing cross-origin content if the user is affirmatively involved in deciding what content is being used.

Solution#5: Chrome and Firefox now allow the client-user to right click the canvas & save the canvas as an image. This is the manual equivalent of using canvas.toDataURL to create an image object and save that image object to the local drive. CORS is satisfied because the user decided if the canvas content was appropriate to save to their local drive and they affirmatively right-click to initiate the download process.

Solution#6: You can use an input element, type='file' to let the client-user select an image. The user can even select an internet URL (http://...). Again, CORS is satisfied because the user is involved in the selection process.

Here's example code showing how to listen for the user to select an image using an input:

// canvas vars
var canvas=document.createElement("canvas");
var ctx=canvas.getContext("2d");

// define max resulting image width,height (after resizing)
var maxW=100;
var maxH=100;

// listen for user to select files
var input = document.getElementById('input');
input.addEventListener('change', handleFiles);

function handleFiles(e) {
  var img = new Image;
  img.onload = function(){
    var iw=img.width;
    var ih=img.height;
    // scale down, if necessary
    if(iw>maxW || ih>maxH){
      var scale=Math.min((maxW/iw),(maxH/ih));
      iw*=scale;
      ih*=scale
    }
    // set canvas width/height to scaled size
    canvas.width=iw;
    canvas.height=ih;
    // draw+scale the img onto the canvas
    ctx.drawImage(img,0,0,iw,ih);
    // create a jpeg URL (with medium quality to save "weight") 
    var jpg=canvas.toDataURL('image/jpeg',0.60);
    // In Demo: add the jpg to the window
    // In production, accumulate jpg's & send to server
    $('<img />',{src:jpg}).appendTo('body');
  }
  // In Demo: Just process the first selected file 
  // In production: process all selected files 
  img.src = URL.createObjectURL(e.target.files[0]);
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>You can even enter a web URI (http://...)</h4>
<input type="file" id="input"/><br>

Solution#7: You can use the new(ish) FileReader to let the user select image(s) to be drawn on the canvas.

// dropDiv event handlers
var dropDiv=document.getElementById("dropDiv");
dropDiv.addEventListener("dragenter", handleDragEnter, false);
dropDiv.addEventListener("dragover", handleDragOver, false);
dropDiv.addEventListener("drop", handleDrop, false);
//
function handleDragEnter(e) {
  e.stopPropagation();
  e.preventDefault();
}
//
function handleDragOver(e) {
  e.stopPropagation();
  e.preventDefault();
}
//
function handleDrop(e) {
  e.stopPropagation();
  e.preventDefault();

  var dt = e.dataTransfer;
  var files = dt.files;

  handleFiles(files);
}

//
function handleFiles(files) {

  for (var i=0;i<files.length;i++) {
    var file = files[i];
    var imageType = /image.*/;

    if (!file.type.match(imageType)) {
      continue;
    }

    var img = document.createElement("img");
    img.classList.add("obj");
    img.file = file;
    preview.appendChild(img);

    var reader=new FileReader();
    reader.onload=(function(aImg){
      return function(e) {
        aImg.onload=function(){
          var canvas=document.createElement("canvas");
          var ctx=canvas.getContext("2d");
          canvas.width=aImg.width;
          canvas.height=aImg.height;
          ctx.drawImage(aImg,0,0);
          document.body.appendChild(canvas);
        }
        // e.target.result is a dataURL for the image
        aImg.src = e.target.result;
      }; 
    })(img);
    reader.readAsDataURL(file);

  } // end for

} // end handleFiles

function calcNewAspect(imgWidth, imgHeight, maxWidth, maxHeight) {
  var ratio = Math.min(maxWidth/imgWidth,maxHeight/imgHeight);
  return {width:imgWidth*ratio,height:imgHeight*ratio };
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
#dropDiv{border:1px solid blue; width:300px;height:300px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Drag 1+ image(s) from desktop to blue dropDiv.</h4>
<div id="dropDiv"></div>
<div id="preview"></div>

To be continued...

There are likely more info-bits that need to be included in this update. Please feel free to comment on my absent mindedness and I'll continue the update. :-)

markE
  • 102,905
  • 11
  • 164
  • 176
  • a few notes : **1** Even if the server is well configured and the ``'s crossOrigin attribute is set, you have to be on a server (be it `http://localhost`) : trying to do a cross-origin request from the `file://` protocol won't work. **2** http://imgur.com doesn't send the proper headers, see [this meta question](http://meta.stackexchange.com/questions/267805/cors-for-stack-imgur) where a proxy service is provided as a workaround, and it's also possible to write your own. – Kaiido Jan 13 '16 at 01:59
  • **3** I would have added that if the crossOrigin attribute is set and supported, but the server doesn't allowed the request, the image won't load at all, which will raise the `error` event on the `` **4** I would have added a note about CORS not being the only way to taint a canvas : IE` element was drawn. – Kaiido Jan 13 '16 at 01:59
  • **5** I would have added a note about the fact that a tainted canvas drawn onto an other canvas will also taint the later. Also, there is no way to "untaint" a canvas, even if `clearRect` is called on the full area of the canvas, the canvas will still be flagged as tainted. You have to create a new canvas and redraw everything except the compromised part in such cases. – Kaiido Jan 13 '16 at 02:00
  • Hello @markE, I appreciate your answer its really helpful to me and other too who are looking for this type of solution, as i mentioned before in comments with @Kaiido, i have only one option is to use `Access-Control-Allow-Origin` so following your `Solution#3:` which is the first and last chance for me to get success in my work. – Kirankumar Dafda Jan 13 '16 at 04:56
  • Also, as @Kaiido mentioned in his 5th point, I have to redraw canvas every time and its already happen in my project whenever i am going through it. So Thanks a lot to you both :) – Kirankumar Dafda Jan 13 '16 at 04:58
  • @Kirankumar, you could also avoid the canvas being tainted by first drawing the unsafe data onto a 1px*1px tester canvas, and then `try{tester.toDataURL(); tainted = false;}catch(e){tester = tester.cloneNode(true); tainted= true;`. – Kaiido Jan 13 '16 at 05:22
  • Yes @Kaiido, I can understand but first of all i have to access an image from the cross domain, and then after able to do any of this operation in it. – Kirankumar Dafda Jan 13 '16 at 05:28