Использование изображений

До сих пор мы создавали наши собственные фигуры и применяли стили к ним. Одна из самых впечатляющих функций <canvas> это возможность использования изображений. Они могут быть использованы для динамического композитинга фото или как фоны графиков, для спрайтов в играх, и так далее. Внешние изображения могут быть использованы в любых поддерживаемых браузером форматах, таких как PNG, GIF, или JPEG. Вы можете даже использовать изображение, произведённое другими canvas элементами на той же странице как источник!

Импортирование изображений в canvas в основном состоит из 2 этапов:

  1. Дав ссылку на HTMLImageElement объект или для другого canvas элемента как источник. Также можно использовать изображение дав ссылку на URL.
  2. Для рисования изображения на canvas используется функция drawImage().

Давайте посмотрим как это сделать.

Использование изображений для рисования

Canvas API может использовать все перечисленные далее типы данных как источник изображения:

HTMLImageElement

Эти изображения созданы, используя конструктор Image(), также как все<img> элементы.

HTMLVideoElement

Используя HTML <video> элемент как источник изображения захватывает текущий кадр из видео и использует его как изображение.

HTMLCanvasElement

Вы можете использовать другой <canvas> элемент как источник изображения.

Эти источники совместно именуемые по типу CanvasImageSource (en-US).

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

Использование изображений из той же страницы

Мы можем получить ссылку на изображение, на той же странице, на canvas с используя один из способов:

  • document.images коллекция
  • The document.getElementsByTagName() метод
  • Если вы знаете id конкретного изображения, который вы хотите использовать, вы можете использовать document.getElementById (), чтобы получить это конкретное изображение

Использование изображений из других доменов

Использование crossorigin атрибута <img> элемент (отображается HTMLImageElement.crossOrigin (en-US) свойства), вы можете запросить разрешение на загрузку другого домена для использования в drawImage(). Если хостинг домен разрешает доступ к междоменному изображению, то изображение может быть использовано в вашем canvas без without tainting it;иначе он может испортить ваш canvas.

Использование других canvas элементов

Как и с обычными изображениями, мы можем получить доступ к другим canvas элементам используя либо document.getElementsByTagName() либо document.getElementById() метод. Проверьте, что в canvas источнике уже что-то нарисовано, прежде чем использовать его в целевом изображении canvas.

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

Создание изображений с нуля

Другой способ это создать новые HTMLImageElement объекты в нашем скрипте. Чтобы это сделать, вы можете использовать удобный Image() конструктор:

js
var img = new Image(); // Создаёт новый элемент изображения
img.src = "myImage.png"; // Устанавливает путь

Когда этот скрипт выполнится, изображение начнёт загружаться.

Если вы попытаетесь вызвать функцию drawImage() перед тем как изображение загрузится, то скрипт ничего не сделает (или, в старых браузерах, может даже выдать исключение). Поэтому вам необходимо использовать событие load, чтобы вы не пытались сделать это прежде, чем изображение загрузится:

js
var img = new Image(); // Создаёт новое изображение
img.addEventListener(
  "load",
  function () {
    // здесь выполняет drawImage функцию
  },
  false,
);
img.src = "myImage.png"; // Устанавливает источник файла

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

Вложение изображения с помощью данных: URL

Другой возможный способ включить изображение это через data: url (en-US). Data URLs позволяет вам полностью определить изображение как Base64 кодированную строку символов прямо в ваш код.

js
var img = new Image(); // Создаёт новый элемент img
img.src =
  "data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==";

Одним из преимуществ data URLs это то что полученное изображение доступно сразу без других запросов туда-обратно на сервер. Другое потенциальное преимущество в том, что также можно инкапсулировать всё в одном файле все ваши CSS, JavaScript, HTML, и изображения, что делает его более портативным в других местах.

Некоторые недостатки этого метода в том что ваше изображение не кешировано, и для изображений с большим размером кодирование url может стать очень долгим процессом.

Использование кадров из видео

Вы также можете использовать кадры из видео представленных <video> элементом (даже если видео не видно). Например, если у вас есть <video> элемент с ID "myvideo", вы можете сделать:

js
function getMyVideo() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    return document.getElementById("myvideo");
  }
}

Эта функция вернёт HTMLVideoElement объект для этого видео, который, как мы упоминали ранее, является одним из объектов, который можно использовать как CanvasImageSource.

Рисование изображений

Как только мы получили ссылку на источник объекта изображения, мы можем использовать метод drawImage() для включения его в canvas. Как мы увидим далее, метод drawImage() перегружен и у него есть несколько вариантов. В базовом варианте он выглядит как:

drawImage(image, x, y)

Рисует изображение, указанное в CanvasImageSource в координатах (x, y).

Примечание: SVG изображения должны указывать ширину и высоту корневого <svg> элемента.

Пример: Простой линейный график

В следующем примере, мы будем использовать внешнее изображение в качестве фона для небольшого линейного графика. Использование фонов может сделать ваш скрипт значительно меньше, потому что мы можем избежать необходимости писать код для создания фона. В этом примере мы используем только один образ, поэтому я использую обработчик событий изображения объекта загрузки для выполнения операторов рисования. drawImage() метод определяющий место фона с координатами (0, 0), которые привязаны к верхнему левому углу canvas.

js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var img = new Image();
  img.onload = function () {
    ctx.drawImage(img, 0, 0);
    ctx.beginPath();
    ctx.moveTo(30, 96);
    ctx.lineTo(70, 66);
    ctx.lineTo(103, 76);
    ctx.lineTo(170, 15);
    ctx.stroke();
  };
  img.src = "backdrop.png";
}

Получившийся график выглядит так:

ScreenshotLive sample

Изменение размеров

Второй вариант метода drawImage() добавляет два новых параметра и позволяет разместить изображение в canvas с изменёнными размерами.

drawImage(image, x, y, width, height)

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

Пример: Тайлинг изображения

В этом примере, мы будем использовать изображение в качестве обоев и повторим его в canvas несколько раз. Это может быть сделано просто через цикл, располагая изменённые изображения на разных позициях. В коде внизу, первый цикл for проходит по рядам. Второй цикл for проходит по колонкам. Изображение уменьшено на треть от реального размера, которое было 50x38 пикселей.

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

js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var img = new Image();
  img.onload = function () {
    for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 3; j++) {
        ctx.drawImage(img, j * 50, i * 38, 50, 38);
      }
    }
  };
  img.src = "rhino.jpg";
}

Получившийся рисунок canvas выглядит так:

ScreenshotLive sample

Нарезка

У третьего и последнего варианта метода drawImage() в дополнении к источнику изображения есть ещё восемь параметров . Он позволяет нам вырезать кусок из изображения, затем изменить его размер и нарисовать его в canvas.

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

В данном изображении, эта функция берёт фрагмент из изображения, в виде прямоугольника, левый верхний угол которого - (sx, sy), ширина и высота - sWidth и sHeight и рисует в canvas, располагая его в точке (dx, dy) и изменяя его размер на указанные величины в dWidth и dHeight.

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

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

Пример: Обрамление изображения

В этом примере, мы будем использовать того же носорога, что и в предыдущем примере, но мы отрежем его голову и включим её в рамку. Изображение рамки это 24-х битный PNG, который включает падающую тень. Так как в 24-х битные PNG изображения включается полный 8-ми битный альфа-канал, в отличие от GIF и 8-битных PNG изображений, он может быть помещён в любой фон, без беспокойства о матовом цвете.

html
<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
    <div style="display:none;">
      <img id="source" src="rhino.jpg" width="300" height="227" />
      <img id="frame" src="canvas_picture_frame.png" width="132" height="150" />
    </div>
  </body>
</html>
js
function draw() {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");

  // Рисуем фрагмент
  ctx.drawImage(
    document.getElementById("source"),
    33,
    71,
    104,
    124,
    21,
    20,
    87,
    104,
  );

  // Рисуем рамку
  ctx.drawImage(document.getElementById("frame"), 0, 0);
}

В этот раз мы применили другой способ загрузки изображения. Вместо загрузки методом создания новых HTMLImageElement объектов, мы включили их как <img> тэги прямо в наш HTML файл и из них выбрали изображения. Изображения скрыты с помощью CSS-свойства display, установленного в "none" для этих изображений.

ScreenshotLive sample

Скрипт, сам по себе, очень простой. Каждому <img> присвоен атрибут ID, который делает удобным их выбор с использованием document.getElementById(). Потом мы просто используем функцию drawImage(), чтобы из первого изображения вырезать фрагмент носорога и вставить его в canvas, затем рисуем рамку сверху, используя второй вызов функции drawImage().

Пример галереи искусства

В последнем примере этой главы, мы построим небольшую галерею искусств. Галерея состоит из таблицы, включающей несколько изображений. Когда страница загрузится, <canvas> элемент вставится в каждое изображение, а вокруг будет нарисована рамка.

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

Код ниже должен говорить сам за себя. Мы проходим циклом через document.images контейнер и соответственно добавляем новые элементы canvas. Возможно следует упомянуть для тех, кто не слишком хорошо знаком с DOM, что для этого используется Node.insertBefore метод. insertBefore() это метод родительского узла (ячейки таблицы) элемента (изображения) перед которым мы хотим вставить наш новый узел (элемент canvas).

html
<html>
  <body onload="draw();">
    <table>
      <tr>
        <td><img src="gallery_1.jpg" /></td>
        <td><img src="gallery_2.jpg" /></td>
        <td><img src="gallery_3.jpg" /></td>
        <td><img src="gallery_4.jpg" /></td>
      </tr>
      <tr>
        <td><img src="gallery_5.jpg" /></td>
        <td><img src="gallery_6.jpg" /></td>
        <td><img src="gallery_7.jpg" /></td>
        <td><img src="gallery_8.jpg" /></td>
      </tr>
    </table>
    <img id="frame" src="canvas_picture_frame.png" width="132" height="150" />
  </body>
</html>

И сюда какую-нибудь CSS для украшения:

css
body {
  background: 0 -100px repeat-x url(bg_gallery.png) #4f191a;
  margin: 10px;
}

img {
  display: none;
}

table {
  margin: 0 auto;
}

td {
  padding: 15px;
}

Связывая все вместе JavaScript рисует наши изображения в рамках:

js
function draw() {
  // Цикл по всем изображениям
  for (var i = 0; i < document.images.length; i++) {
    // Не добавляет canvas для изображения рамки
    if (document.images[i].getAttribute("id") != "frame") {
      // Создаёт элемент canvas
      var canvas = document.createElement("canvas");
      canvas.setAttribute("width", 132);
      canvas.setAttribute("height", 150);

      // Вставляет перед изображением
      document.images[i].parentNode.insertBefore(canvas, document.images[i]);

      var ctx = canvas.getContext("2d");

      // Рисует изображение в canvas
      ctx.drawImage(document.images[i], 15, 20);

      // Добавляет рамку
      ctx.drawImage(document.getElementById("frame"), 0, 0);
    }
  }
}

Контроль изменений размеров изображения

Как было отмечено ранее, изменение размеров изображений может привести к размытости или к шуму в процессе преобразования. Вы можете использовать контекст рисования imageSmoothingEnabled (en-US) свойства, чтобы контролировать использование сглаживающего алгоритма, когда изменяющиеся изображения в вашем контексте. Обычно это свойство установлено в true, означая, что изображения будут сглажены во время изменения размеров. Вы можете отключить это свойство так:

js
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;