In Chrome I get the following error on jsfiddle when trying to test canvas drawing images from a remote url.
Error: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
On S3 bucket I have the following CORS policy, which allows for cross resource sharing:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
</CORSRule>
</CORSConfiguration>
If I curl the image using jsfiddle as the origin I get:
curl -H 'Origin: https://fiddle.jshell.net' -I 'https://i.ezr.io/products3/472_ARDSCR.jpg?h=45&w=165&fit=scale'
HTTP/1.1 200 OK
Cache-Control: public,max-age=31536000
Last-Modified: Wed, 07 Feb 2018 23:42:47 GMT
Server: imgix-fe
Content-Length: 6371
Accept-Ranges: bytes
Date: Fri, 16 Mar 2018 17:10:20 GMT
Age: 3173253
Connection: keep-alive
Content-Type: image/jpeg
Access-Control-Allow-Origin: *
X-Content-Type-Options: nosniff
X-Served-By: cache-lax8630-LAX, cache-sea1027-SEA
X-Cache: HIT, HIT
var Rack = {
init: function(params) {
Rack.conf = params;
Rack.bindEvents();
},
bindEvents: function() {
Rack.conf.scaleDown.addEventListener("click", function() {
Rack.clearCanvas();
Rack.conf.scaleW = Rack.round(Rack.conf.scaleW - .1, 1);
Rack.conf.scaleY = Rack.round(Rack.conf.scaleY - .1, 1);
if (Rack.conf.scaleW > 0 && Rack.conf.scaleY > 0) {
Rack.build();
}
});
Rack.conf.resetScale.addEventListener("click", function() {
Rack.clearCanvas();
Rack.conf.scaleW = 1;
Rack.conf.scaleY = 1;
Rack.build(true);
});
Rack.getRackJSON();
Rack.setNumImages();
Rack.build();
},
getRackJSON: function() { // will send ajax call based on item being added to the rack, will return json rack
Rack.conf.rack = {
"awards": [{
"src": "https://i.ezr.io/products3/472_ARDSCR.jpg?h=45&w=165&fit=scale",
"name": "Army Distinguished Service Cross",
"sku": "472_ARDSCR",
"x": 0,
"y": 0,
"width": 165,
"height": 45,
"attachments": [{
"src": "https://i.ezr.io/products3/913NP.png?h=20",
"name": "Bronze Oak Leaf",
"sku": "913NP",
"x": 73,
"y": 13,
"width": 20,
"height": 20
}]
},
{
"src": "https://i.ezr.io/products3/470_ARDDR.jpg?h=45&w=165&fit=scale",
"name": "Department of Defense Distinguished Service",
"sku": "470_ARDDR",
"x": 165,
"y": 0,
"width": 165,
"height": 45
}
]
};
},
setNumImages() {
for (var i = 0; i < Rack.conf.rack.awards.length; i++) {
var award = Rack.conf.rack.awards[i];
++Rack.conf.numImages;
if (award.hasOwnProperty('attachments')) {
Rack.conf.numImages += award.attachments.length;
}
}
},
loadImages: function(callback) {
var numImagesLoaded = 0;
for (var i = 0; i < Rack.conf.rack.awards.length; i++) {
var award = Rack.conf.rack.awards[i];
Rack.conf.images[award.sku] = new Image();
Rack.conf.images[award.sku].onload = function() {
if (++numImagesLoaded >= Rack.conf.numImages) {
callback();
}
}
Rack.conf.images[award.sku].src = award.src;
if (award.hasOwnProperty('attachments')) {
for (var j = 0; j < award.attachments.length; j++) {
var attachment = award.attachments[j];
Rack.conf.images[attachment.sku] = new Image();
Rack.conf.images[attachment.sku].crossOrigin = 'anonymous';
Rack.conf.images[attachment.sku].onload = function() {
if (++numImagesLoaded >= Rack.conf.numImages) {
callback();
}
}
Rack.conf.images[attachment.sku].src = attachment.src;
}
}
}
},
build: function(reset) {
if (Rack.conf.outputType === 'jpg') {
Rack.conf.ctx.fillStyle = "#ffffff";
Rack.conf.ctx.fillRect(0, 0, Rack.conf.c.width, Rack.conf.c.height);
}
reset === true ? Rack.conf.ctx.setTransform(1, 0, 0, 1, 0, 0) : Rack.conf.ctx.scale(Rack.conf.scaleW, Rack.conf.scaleY);
Rack.loadImages(function() {
for (var i = 0; i < Rack.conf.rack.awards.length; i++) {
var award = Rack.conf.rack.awards[i];
Rack.conf.ctx.drawImage(Rack.conf.images[award.sku], award.x, award.y, award.width, award.height);
if (award.hasOwnProperty('attachments')) {
for (var j = 0; j < award.attachments.length; j++) {
var attachment = award.attachments[j];
Rack.conf.ctx.drawImage(Rack.conf.images[attachment.sku], attachment.x, attachment.y, attachment.width, attachment.height);
}
}
}
});
Rack.conf.imageData = Rack.conf.c.toDataURL((Rack.conf.outputType === 'jpg' ? 'image/jpeg' : null), (Rack.conf.outputType === 'jpg' ? 1.0 : null));
},
clearCanvas: function() {
Rack.conf.ctx.clearRect(0, 0, Rack.conf.c.width, Rack.conf.c.height);
},
round: function(number, precision) {
var factor = Math.pow(10, precision);
var tempNumber = number * factor;
var roundedTempNumber = Math.round(tempNumber);
return roundedTempNumber / factor;
}
};
Rack.init({
c: document.getElementById("myCanvas"),
ctx: document.getElementById("myCanvas").getContext("2d"),
outputType: 'png',
scaleDown: document.getElementById('scale-down'),
resetScale: document.getElementById('reset-scale'),
images: {},
numImages: 0,
awards: null,
imageData: null,
scaleW: 1,
scaleY: 1,
rack: null
});
<canvas id="myCanvas" width="330" height="45">Your browser does not support the HTML5 canvas tag.</canvas>
<div>
<button id="scale-down" style="cursor:pointer;">
Scale Down
</button>
<button id="reset-scale" style="cursor:pointer;">
Reset
</button>
</div>
You already know the answer to your question now, which is adding Rack.conf.images[award.sku].crossOrigin = 'anonymous';
Below is the thread with the similar issue explaining the need
https://github.com/locomotivecms/engine/issues/1152
toDataURL throw Uncaught Security exception
https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
What is a "tainted" canvas?
Although you can use images without CORS approval in your canvas, doing so taints the canvas. Once a canvas has been tainted, you can no longer pull data back out of the canvas. For example, you can no longer use the canvas toBlob(), toDataURL(), or getImageData() methods; doing so will throw a security error.
This protects users from having private data exposed by using images to pull information from remote web sites without permission.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With