'toDataURL' на 'HTMLCanvasElement': испорченные холсты нельзя экспортировать

В Chrome я получаю следующую ошибку в jsfiddle при попытке протестировать изображения рисования холста с удаленного URL-адреса.

Ошибка: не удалось выполнить toDataURL для HTMLCanvasElement: испорченные холсты не могут быть экспортированы.

В сегменте S3 у меня есть следующая политика CORS, которая разрешает совместное использование ресурсов между собой:

<?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>

Если я скручиваю изображение, используя jsfiddle в качестве источника, я получаю:

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>


person Zelf    schedule 27.10.2017    source источник
comment
Это сервер размещения изображений, который позволяет использовать кросс-домен, добавляя в ответ соответствующий заголовок CORS. Если этого нет в ответе сервера, вы не можете получить доступ к пиксельным данным, содержащимся в изображении.   -  person Blindman67    schedule 28.10.2017
comment
Access-Control-Allow-Origin: * возвращается, поэтому проблем с CORS быть не должно.   -  person Zelf    schedule 16.03.2018
comment
Проблема решена: отсутствовал Rack.conf.images [Award.sku] .crossOrigin = 'anonymous';   -  person Zelf    schedule 16.03.2018
comment
Эти два URL-адреса, вероятно, объясняют, почему вам это нужно. github.com/locomotivecms/engine/issues/1152 и stackoverflow.com/questions/20027839/   -  person Tarun Lalwani    schedule 16.03.2018


Ответы (2)


Вы уже знаете ответ на свой вопрос, который добавляет Rack.conf.images[award.sku].crossOrigin = 'anonymous';

Ниже приведена ветка с аналогичной проблемой, объясняющая необходимость

https://github.com/locomotivecms/engine/issues/1152

toDataURL генерирует исключение Uncaught Security

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

Что такое испорченный холст?

Хотя вы можете использовать изображения на холсте без утверждения CORS, это портит холст. После того, как холст был испорчен, вы больше не сможете извлекать данные из холста. Например, вы больше не можете использовать методы холста toBlob (), toDataURL () или getImageData (); это вызовет ошибку безопасности.

Это защищает пользователей от раскрытия личных данных с помощью изображений для извлечения информации с удаленных веб-сайтов без разрешения.

person Tarun Lalwani    schedule 23.03.2018

Проблема решена: отсутствовал Rack.conf.images [Award.sku] .crossOrigin = 'anonymous';

person Zelf    schedule 23.03.2018