Использование изображений
До сих пор мы создавали собственные фигуры и применяли стили к ним. Одной из более интересных функций <canvas> является возможность использования изображений. Они могут использоваться для динамического фотокомпозитинга или в качестве фонов для графиков, для спрайтов в играх и так далее. Внешние изображения могут использоваться в любом формате, поддерживаемом браузером, таком как PNG, GIF или JPEG. Вы даже можете использовать изображение, созданное другими элементами canvas на той же странице, в качестве источника!
Импорт изображений в холст в основном происходит в два этапа:
- Получите ссылку на объект
HTMLImageElementили на другой элемент холста в качестве источника. Также возможно использовать изображения, предоставив URL. - Отобразите изображение на холсте с помощью функции
drawImage().
Давайте посмотрим, как это сделать.
Получение изображений для рисования
API холста может использовать любой из следующих типов данных в качестве источника изображения:
HTMLImageElement-
Это изображения, созданные с помощью конструктора
Image(), а также любой элемент<img>. SVGImageElement-
Это изображения, вставленные с помощью элемента
<image>. HTMLVideoElement-
Использование HTML-элемента
<video>в качестве источника изображения извлекает текущий кадр из видео и использует его как изображение. HTMLCanvasElement-
Вы можете использовать другой элемент
<canvas>в качестве источника изображения. ImageBitmap-
Изображение в растровом формате, возможно, обрезанное. Такие типы используются для извлечения части изображения, спрайта, из более крупного изображения.
OffscreenCanvas-
Особый вид
<canvas>, который не отображается и подготавливается без отображения. Использование такого источника изображения позволяет переключиться на него, не отображая содержимое пользователю. VideoFrame-
Изображение, представляющее один кадр видео.
Существует несколько способов получить изображения для использования на холсте.
Использование изображений с той же страницы
Мы можем получить ссылку на изображения на той же странице, что и холст, используя один из следующих методов:
- Коллекция
document.images - Метод
document.getElementsByTagName() - Если вам известен идентификатор конкретного изображения, которое вы хотите использовать, вы можете использовать
document.getElementById()для получения этого конкретного изображения
Использование изображений с других доменов
Используя атрибут crossorigin элемента <img> (отражённый свойством HTMLImageElement.crossOrigin), вы можете запросить разрешение на загрузку изображения с другого домена для использования в вашем вызове drawImage(). Если домен хостинга разрешает доступ к изображению через разные домены, изображение может быть использовано в вашем холсте без проблем; в противном случае использование изображения приведет к загрязнению холста.
Использование других элементов холста
Так же, как и с обычными изображениями, мы получаем доступ к другим элементам холста, используя метод document.getElementsByTagName() или document.getElementById(). Убедитесь, что вы что-то нарисовали на исходном холсте перед использованием его на целевом холсте.
Одно из более практичных применений этого — использование второго элемента холста в качестве миниатюры другого, большего холста.
Создание изображений с нуля
Ещё один вариант — создание новых объектов HTMLImageElement в нашем скрипте. Для этого у нас есть удобный конструктор Image():
const img = new Image(); // Create new img element img.src = "myImage.png"; // Set source path
Когда этот скрипт выполняется, загрузка изображения начинается, но если вы попытаетесь вызвать drawImage() до завершения загрузки изображения, ничего не произойдёт. Более старые браузеры могут даже выбросить исключение, поэтому необходимо убедиться, что вы используете событие загрузки, чтобы не отображать изображение на холсте до его готовности:
const ctx = document.getElementById("canvas").getContext("2d");
const img = new Image();
img.addEventListener("load", () => {
ctx.drawImage(img, 0, 0);
});
img.src = "myImage.png";
Если вы используете одно внешнее изображение, это может быть хороший подход, но когда вы хотите использовать много изображений или откладывать загрузку ресурсов, вам, вероятно, придётся подождать, пока все файлы будут доступны, прежде чем отображать изображение на холсте. Примеры ниже, связанные с несколькими изображениями, используют асинхронную функцию и Promise.all, чтобы подождать загрузки всех изображений перед вызовом drawImage():
async function draw() {
// Wait for all images to be loaded:
await Promise.all(
Array.from(document.images).map(
(image) =>
new Promise((resolve) => image.addEventListener("load", resolve)),
),
);
const ctx = document.getElementById("canvas").getContext("2d");
// call drawImage() as usual
}
draw();
Вставка изображения с помощью URL-адреса data:
Ещё один возможный способ включения изображений — с помощью data: URL. Data URL позволяют полностью определить изображение как строку Base64-кодированных символов прямо в вашем коде.
const img = new Image(); // Create new img element img.src = "data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==";
Одним из преимуществ data URL является то, что полученное изображение становится доступным сразу же без дополнительного обращения к серверу. Ещё одним потенциальным преимуществом является то, что все ваши CSS, JavaScript, HTML и изображения можно поместить в один файл, что делает его более портативным для других мест.
Некоторыми недостатками этого метода являются то, что ваше изображение не кэшируется, и для больших изображений закодированный URL может быть довольно длинным.
Использование кадров из видео
Вы также можете использовать кадры из видео, представленного элементом <video> (даже если видео не отображается). Например, если у вас есть элемент <video> с идентификатором "myVideo", вы можете сделать следующее:
function getMyVideo() {
const canvas = document.getElementById("canvas");
if (canvas.getContext) {
const ctx = canvas.getContext("2d");
return document.getElementById("myVideo");
}
}
Это возвращает объект HTMLVideoElement для видео, который, как уже говорилось, может использоваться в качестве источника изображения для холста.
Отображение изображений
После получения ссылки на объект исходного изображения мы можем использовать метод drawImage() для его отображения на холсте. Как мы увидим позже, метод drawImage() перегружен и имеет несколько вариантов. В самом простом виде он выглядит так:
drawImage(image, x, y)-
Отображает изображение, указанное параметром
imageв координатах (x,y).
Примечание: Изображения SVG должны указывать ширину и высоту в корневом элементе <svg>.
Пример: небольшая линейная диаграмма
В следующем примере мы будем использовать внешнее изображение в качестве фона для небольшой линейной диаграммы. Использование фонов может значительно уменьшить размер вашего скрипта, так как мы можем избежать необходимости генерировать фон кодом. В этом примере мы используем только одно изображение, поэтому я использую обработчик событий load объекта изображения для выполнения операторов рисования. Метод drawImage() размещает фон в координатах (0, 0), что является верхним левым углом холста.
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
const img = new Image();
img.onload = () => {
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";
}
draw();
Получившаяся диаграмма выглядит так:
Масштабирование
Второй вариант метода drawImage() добавляет два новых параметра и позволяет размещать масштабированные изображения на холсте.
drawImage(image, x, y, width, height)-
Это добавляет параметры
widthиheight, которые указывают размер, к которому следует масштабировать изображение при отображении его на холсте.
Пример: применение узора изображения
В этом примере мы будем использовать изображение в качестве обоев и повторять его несколько раз на холсте. Это делается путем циклирования и размещения масштабированных изображений в разных позициях. В приведенном ниже коде первый for цикл проходит по строкам. Второй for цикл проходит по столбцам. Изображение масштабируется до одной трети от его исходного размера, который составляет 50x38 пикселей.
Примечание: Изображения могут стать размытыми при увеличении масштаба или зернистыми, если они уменьшены слишком сильно. Масштабирование, вероятно, лучше не выполнять, если в изображении есть текст, который должен оставаться разборчивым.
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
const img = new Image();
img.onload = () => {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 3; j++) {
ctx.drawImage(img, j * 50, i * 38, 50, 38);
}
}
};
img.src = "https://mdn.github.io/shared-assets/images/examples/rhino.jpg";
}
draw();
Полученный холст выглядит так:
Разбиение
Третий и последний вариант метода drawImage() имеет восемь параметров помимо исходного изображения. Он позволяет нам вырезать фрагмент исходного изображения, затем масштабировать и отобразить его на нашем холсте.
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)-
Учитывая
image, эта функция берет область исходного изображения, заданную прямоугольником с левым верхним углом (sx,sy) и шириной и высотойsWidthиsHeight, и отображает ее на холсте, помещая ее на холст в позиции (dx,dy) и масштабируя ее до размера, заданногоdWidthиdHeight, сохраняя его соотношение сторон.
Чтобы по-настоящему понять, что это делает, может помочь посмотреть на это изображение:
Первые четыре параметра определяют расположение и размер фрагмента на исходном изображении. Последние четыре параметра определяют прямоугольник, в который нужно отобразить изображение на целевом холсте.
Разбиение может быть полезным инструментом, когда вы хотите создать композиции. Вы можете иметь все элементы в одном файле изображения и использовать этот метод для создания полной графики. Например, если вы хотите создать график, вы можете иметь изображение PNG, содержащее весь необходимый текст в одном файле, и в зависимости от ваших данных можете довольно легко изменить масштаб своего графика. Еще одним преимуществом является то, что вам не нужно загружать каждое изображение по отдельности, что может повысить производительность загрузки.
Пример: обрамление изображения
В этом примере мы будем использовать того же носорога, что и в предыдущем примере, но вырежем его голову и добавим ее в рамку. Картинка рамки — 24-битный PNG, который включает тень. Поскольку 24-битные изображения PNG включают полный 8-битный альфа-канал, в отличие от GIF и 8-битных изображений PNG, их можно разместить на любом фоне, не беспокоясь о цвете заливки.
<canvas id="canvas" width="150" height="150"></canvas>
<div style="display: none;">
<img
id="source"
src="https://mdn.github.io/shared-assets/images/examples/rhino.jpg"
width="300"
height="227" />
<img id="frame" src="canvas_picture_frame.png" width="132" height="150" />
</div>
async function draw() {
// Wait for all images to be loaded.
await Promise.all(
Array.from(document.images).map(
(image) =>
new Promise((resolve) => image.addEventListener("load", resolve)),
),
);
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// Draw slice
ctx.drawImage(
document.getElementById("source"),
33,
71,
104,
124,
21,
20,
87,
104,
);
// Draw frame
ctx.drawImage(document.getElementById("frame"), 0, 0);
}
draw();
Мы подошли к загрузке изображений иначе. Вместо загрузки с помощью создания новых объектов HTMLImageElement мы включили их как теги <img> в наш исходный HTML и извлекли изображения из них при рисовании на холсте. Изображения скрыты с помощью CSS-свойства display со значением none для этих изображений.
Каждому тегу <img> присваивается атрибут ID, поэтому есть один для source и один для frame, что упрощает их выбор с помощью document.getElementById(). Мы используем Promise.all, чтобы дождаться загрузки всех изображений перед вызовом drawImage(). drawImage() вырезает носорога из первого изображения и масштабирует его на холсте. Наконец, мы рисуем рамку с помощью второго вызова drawImage().
Пример художественной галереи
В заключительном примере этой главы мы создадим небольшую художественную галерею. Галерея состоит из таблицы, содержащей несколько изображений. При загрузке страницы для каждого изображения вставляется элемент <canvas>, и вокруг него рисуется рамка.
В данном случае каждое изображение имеет фиксированную ширину и высоту, как и рамка, нарисованная вокруг них. Вы можете улучшить скрипт, чтобы он использовал ширину и высоту изображения для точной подгонки рамки вокруг него.
В приведенном ниже коде мы используем Promise.all, чтобы дождаться загрузки всех изображений перед отрисовкой любых изображений на холсте. Мы перебираем контейнер document.images и добавляем новые элементы холста для каждого из них. Еще одним моментом является использование метода Node.insertBefore. insertBefore() — метод родительского узла (ячейки таблицы) элемента (изображения), перед которым мы хотим вставить наш новый узел (элемент холста).
<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" />
И вот CSS для красивого вида:
body {
background: 0 -100px repeat-x url(bg_gallery.png) #4f191a;
margin: 10px;
}
img {
display: none;
}
table {
margin: 0 auto;
}
td {
padding: 15px;
}
Весь код для рисования изображений в рамке:
async function draw() {
// Wait for all images to be loaded.
await Promise.all(
Array.from(document.images).map(
(image) =>
new Promise((resolve) => image.addEventListener("load", resolve)),
),
);
// Loop through all images.
for (const image of document.images) {
// Don't add a canvas for the frame image
if (image.getAttribute("id") !== "frame") {
// Create canvas element
const canvas = document.createElement("canvas");
canvas.setAttribute("width", 132);
canvas.setAttribute("height", 150);
// Insert before the image
image.parentNode.insertBefore(canvas, image);
ctx = canvas.getContext("2d");
// Draw image to canvas
ctx.drawImage(image, 15, 20);
// Add frame
ctx.drawImage(document.getElementById("frame"), 0, 0);
}
}
}
draw();
Управление поведением масштабирования изображений
Как уже упоминалось ранее, масштабирование изображений может привести к появлению размытых или блочных артефактов из-за процесса масштабирования. Вы можете использовать свойство контекста рисования imageSmoothingEnabled, чтобы управлять использованием алгоритмов сглаживания изображений при масштабировании изображений в вашем контексте. По умолчанию это true, что означает, что изображения будут сглаживаться при масштабировании.
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images