Разрешение использования изображений из разных источников и canvas

HTML предоставляет атрибут crossorigin для изображений, которые в сочетании с соответствующим заголовком CORS позволяют использовать изображения, определённые элементом <img>, загруженные из внешних источников, в <canvas> , как если бы они были загружены из текущего источника.

Дополнительные сведения об использовании атрибута crossorigin смотрите в разделе атрибуты параметров CORS.

Безопасность и испорченные холсты canvas

Поскольку пиксели в растровом изображении canvas могут поступать из различных источников, включая изображения или видео, полученные с других хостов, неизбежно могут возникнуть проблемы с безопасностью.

Как только вы рисуете на холсте любые данные, которые были загружены из другого источника без одобрения CORS, холст становится испорченным. Испорченный холст - это тот, который больше не считается безопасным, и любые попытки получить данные изображения с холста вызовут исключение.

Если источником внешнего содержимого является элемент HTML <img> или SVG <svg>, то попытка извлечения содержимого холста не допускается.

Если внешнее содержимое поступает из изображения, полученного либо из HTMLCanvasElement, либо из ImageBitMap, и источник изображения не соответствует тем же правилам происхождения, попытки прочитать содержимое холста блокируются.

Вызов любого из следующих методов на испорченном холсте приведёт к ошибке:

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

Хранение изображений из внешнего источника

В этом примере мы хотим разрешить извлечение изображений из внешнего источника и сохранение их в локальном хранилище. Реализация этого требует настройки сервера, а также написания кода для самого веб-сайта.

Конфигурация веб-сервера

Первое, что нам нужно, - это сервер, настроенный на размещение изображений с заголовком Access-Control-Allow-Origin, настроенным на разрешение доступа к файлам изображений из разных источников.

Давайте предположим, что мы обслуживаем наш сайт с помощью Apache. Рассмотрим стандартный файл конфигурации сервера Apache HTML5 для образов CORS, показанный ниже:

xml
<IfModule mod_setenvif.c>
  <IfModule mod_headers.c>
    <FilesMatch "\.(bmp|cur|gif|ico|jpe?g|png|svgz?|webp)$">
      SetEnvIf Origin ":" IS_CORS
      Header set Access-Control-Allow-Origin "*" env=IS_CORS
    </FilesMatch>
  </IfModule>
</IfModule>

Вкратце, это настраивает сервер на разрешение графических файлов (тех, что с расширениями ".bmp", ".cur", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".svg", ".svgz" и ".webp") для получения доступа из любой точки интернета.

Реализация возможности сохранения

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

Ключевым моментом является использование атрибута crossorigin путём установки crossOrigin (en-US) в элементе HTMLImageElement , на который будет загружено изображение. Это даёт браузеру команду на запрос доступа к другому источнику при попытке загрузить данные изображения.

Запуск загрузки

Код, который запускает загрузку (скажем, когда пользователь нажимает кнопку "Загрузить"), выглядит следующим образом:

js
function startDownload() {
  let imageURL =
    "https://cdn.glitch.com/4c9ebeb9-8b9a-4adc-ad0a-238d9ae00bb5%2Fmdn_logo-only_color.svg?1535749917189";

  downloadedImg = new Image();
  downloadedImg.crossOrigin = "Anonymous";
  downloadedImg.addEventListener("load", imageReceived, false);
  downloadedImg.src = imageURL;
}

Здесь мы используем жёстко закодированный URL-адрес (imageURL), но он запросто может поступать откуда угодно. Чтобы начать загрузку изображения, мы создаём новый объект HTMLImageElement с помощью конструктора Image(). Затем изображение настраивается так, чтобы разрешить загрузку из другого источника. Для этого его атрибут crossOrigin устанавливается на "Anonymous" (то есть разрешение неавторизованной загрузки изображения из перекрёстного источника). Обработчик событий добавляется к событию load (en-US), запускаемому на элементе изображения, что означает, что данные изображения были получены.

Наконец, атрибут src (en-US) изображения устанавливается в URL-адрес загружаемого изображения; это инициирует начало загрузки.

Получение и сохранение изображения

Код, обрабатывающий недавно загруженные изображения, находится в методе imageReceived():

js
function imageReceived() {
  let canvas = document.createElement("canvas");
  let context = canvas.getContext("2d");

  canvas.width = downloadedImg.width;
  canvas.height = downloadedImg.height;

  context.drawImage(downloadedImg, 0, 0);
  imageBox.appendChild(canvas);

  try {
    localStorage.setItem("saved-image-example", canvas.toDataURL("image/png"));
  } catch (err) {
    console.log("Error: " + err);
  }
}

imageReceived() вызывается для обработки события "load" в элементе HTMLImageElement, который получает загруженное изображение. Это событие срабатывает, как только все загруженные данные становятся доступными. Он начинается с создания нового элемента <canvas>, который мы будем использовать для преобразования изображения в URL-адрес данных и получения доступа к контексту 2D-рендеринга холста (CanvasRenderingContext2D) в переменной context.

Размер холста настраивается в соответствии с полученным изображением, затем изображение рисуется на холсте с помощью drawImage(). Затем холст вставляется в документ, чтобы изображение было видно.

Теперь пришло время действительно сохранить изображение локально. Для этого мы используем механизм локального хранения Web Storage API, доступ к которому осуществляется через localStorage глобально. Метод canvas toDataURL() используется для преобразования изображения в data:// URL, представляющий изображение PNG, которое затем сохраняется в локальном хранилище с помощью setItem().

Вы можете проверить или переделать этот пример на Glitch.

Смотрите также