7 января 2011 в 14:04

HTML5 CANVAS шаг за шагом: Изображения tutorial

CANVAS шаг за шагом:
  1. Основы
  2. Изображения
  3. Понг
  4. Пятнашки

Продолжение статьи про рисование на холсте, в которой мы научимся использовать изображения. Естественно рисовать на холсте примитивами очень не удобно и требует определённых трудозатрат, и результат иногода явно хромает качеством. Поэтому естественно в canvas api предусмотрено взаимодействие с изображениями. Добавление изображения условно можно разделить на два шага: создание JavaScript объекта Image, а второй и заключительный шаг это отрисовка изображения на холсте при помощи функции drawImage. Рассмотрим оба шага подробнее.

Создание нового графического объекта:
var img = new Image();  // Создание нового объекта изображения
img.src = 'image.png';  // Путь к изображению которое необходимо нанести на холст

Кстати в качестве источника изображения, можно указать вот такую строку в которой изображение и описанно:
img.src = 'data:image/gif;base64,R0lGODlhDAAMAOYAANPe5Pz//4KkutDb4szY3/b+/5u5z/3//3KWrfn//8rk8naasYGkuszY4Mbg8qG+0dzv9tXg5sTg8t/o7vP8/4iqv9ft9NPe5qfD1Mfc56O/0YKlu+Lr8M3Z4JCwxuj2/Of0+eDz9+rw9Z68z8/n8sHe8sbT3Ju6zuDv96nE1Onw9Nbh6cvX39Hq89Hq8u77/srW3tbh54Kku8ba56TD1u37/vL8/vL8/9ft9ebu8+Ps8bzM1Ymsw7XR4Nnj6Yanvsnj8qrI2Or2/NTf5tvl68vY3+r3/HqdtNji6OXt8eDz+dLc477c7bDO3t7n7d7v9s3Z4dbs9N/y98Pd6PX+/8/b4f7//+Hp7tDo8vv//+fu84GjunKWro6uxHqctOfu9P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAMAAwAAAeEgCJfg4RfWlo5KlpgjI2OOklWBwcBAVmXCQlXHAUFVBkGBjMUNzZOEy81IF2sXUZCH0QrDyhPGzICAkohUj4XHhoQKQsLGDgWUTFIJxUjUy0uWNIkQxE9W9gMDD9BCgpLAEBNXl5H5F40DlUDEkxc71wICDwlDQBQHQ0EBEUsJjswBgQCADs=';


1. drawImage


Теперь мы перейдём к рисованию изображения на холсте. Для этого существует функция drawImage.
drawImage(image, x, y) // Где x и y это координаты левого верхнего угла изображения, а первый параметр это изображение

Стоит отметить что загрузка изображения происходит сразу после присвоения объекту источника изображения, и если оно не загрузится полностью к моменту вызова функции отрисовки, то оно попросту не будет нарисовано на холсте. Для избежания этой ситуации используется такая конструкция:
var img = new Image();      // Новый объект
  img.onload = function() { // Событие которое будет исполнено в момент когда изображение будет загружено
    /*
	  Какие-либо действия
	*/
}
img.src = 'myImage.png';    // Путь к изображению

Вот мы наверное и дошли до момента когда можно рассмотреть элементарный пример:
<!doctype html> 
<html>
  <meta charset='utf-8'>
  <head>
    <title>imgExample</title>
  </head>
  <body>
    <canvas id='example'>Обновите браузер</canvas>
    <script>
      var example = document.getElementById("example"),
        ctx       = example.getContext('2d'), // Контекст
        pic       = new Image();              // "Создаём" изображение
      pic.src    = 'http://habrahabr.ru/i/nocopypast.png';  // Источник изображения, позаимствовано на хабре
      pic.onload = function() {    // Событие onLoad, ждём момента пока загрузится изображение
        ctx.drawImage(pic, 0, 0);  // Рисуем изображение от точки с координатами 0, 0
      }
    </script>
  </body>
</html>

Ссылка на фидл с примерами для этой стать.

2. Тянем-потянем


Но если бы всё ограничивалось простым рисованием изображением, то отдельную статью можно было бы не писать, а ограничиться подпунктом «Изображения» в предыдущем посте. Итак теперь мы попытаемся масштабировать изображение и для этого существует ещё один способ вызова функции drawImage:
drawImage(image, x, y, width, height)  //  параметры width, height меняют ширину и высоту изображения

Возьмём предыдущий пример и внесём в drawImage некоторые изменения:
ctx.drawImage(pic, 0, 0, 300, 150);

Ссылка на фидл с примерами для этой стать.

3. Рисуем фрагмент изображения


Третий вызов drawImage с восемью параметрами, выглядит приблизительно так:
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
// Первый параметр указывает на изображение
// sx, sy, sWidth, sHeight указывают параметры фрагмента на изображение-источнике
// dx, dy, dWidth, dHeight ответственны за координаты отрисовки фрагмента на холсте

Возьмём всё тот же пример и подкорректируем функцию drawImage в третий раз:
ctx.drawImage(pic, 25, 42, 85, 55, 0, 0, 170, 110);

Ссылка на фидл с примерами для этой стать.

4. Задача


Теперь осталось закрепить всё пройденное на практическом примере. Как и в том примере это будет небольшая карта, только не из какой-то существовавшей игры, а придуманной нами. Для того что бы он нормально работал нам необходимо будет создать в любом графическом редакторе изображение составленное из фрагментов которые нам будут нужны что бы нарисовать дорожку, домик и полянку. Вообще фрагменты карты называются тайлами, а файл в котором они все собраны в одно изображение называется тайлсетом. Вот это изображение я нарисовал собственноручно в программе Pinta под Ubuntu.
image
Итак, размерность будет 8 на 8 квадратных блоков шириной 32 пиксела. На карте нам необходимо будет изобразить домик и дорожку. Элементы домика нарисованы ручками, можно сказать каждый кирпичик. Траву и песок делалось путём заливки облости соответствующим цветом и добавки шума. Всё конечно представленно очень примитивно, но показательно.
Рассмотрим с пинцетом такой кусок кода как var map = [[{x:1, y: 4}… значения x и y указывают какой элемент из картинки брать. Т.е. если исходный рисунок разбить на квадрат 32×32 то станет понятней.
И форэкзампл:
<!doctype html> 
<html>
<meta charset='utf-8'>
  <head>
    <title>imgExample</title>
  </head>
    <body>
      <canvas id='example'>Обновите браузер</canvas>
    <script>
        var example = document.getElementById('example'), // Задаём контекст
          ctx       = example.getContext('2d'),           // Контекст холста
          cellSize  = 32,                                 // Размер одной ячейки на карте
          pic       = new Image(),                        // "Создаём" изображение
          map       =                                     // Карта уровня двумерным массивом
          [
            [{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4}], // 1ый ряд
            [{x:1,y:4},{x:1,y:1},{x:2,y:1},{x:3,y:1},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4}], // 2ый ряд
            [{x:1,y:4},{x:1,y:2},{x:2,y:2},{x:3,y:2},{x:1,y:4},{x:1,y:3},{x:1,y:3},{x:1,y:3}], // 3ый ряд
            [{x:1,y:4},{x:3,y:4},{x:2,y:3},{x:3,y:4},{x:1,y:4},{x:1,y:3},{x:1,y:4},{x:1,y:4}], // 4ый ряд
            [{x:1,y:4},{x:3,y:4},{x:2,y:4},{x:3,y:4},{x:1,y:4},{x:1,y:3},{x:1,y:4},{x:1,y:4}], // 5ый ряд
            [{x:1,y:4},{x:1,y:4},{x:1,y:3},{x:1,y:4},{x:1,y:4},{x:1,y:3},{x:1,y:4},{x:1,y:4}], // 6ый ряд
            [{x:1,y:4},{x:1,y:4},{x:1,y:3},{x:1,y:3},{x:1,y:3},{x:1,y:3},{x:1,y:4},{x:1,y:4}], // 7ый ряд
            [{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4},{x:1,y:4}]  // 8ый ряд
          ]; // Первая и вторая координата (x и y соответственно) задают фрагмент в исходном изображении
        // Размер холста равный 8х8 клеток
        example.width  = 8 * cellSize;
        example.height = 8 * cellSize;
        pic.src = 'https://habrastorage.org/getpro/habr/post_images/e85/727/cb1/e85727cb1a88099325eaf5b243d4c41f.png';
        pic.onload = function() {  // Событие onLoad, ждём момента пока загрузится изображение
          for (var j = 0 ; j < 8; j ++)
            for (var i = 0; i < 8; i ++)
              // перебираем все значения массива 'карта' и в зависимости от координат вырисовываем нужный нам фрагмент
              ctx.drawImage(pic, (map[i][j].x-1)*cellSize, (map[i][j].y-1)*cellSize, 32, 32, j*cellSize, i*cellSize, 32, 32);
        }
    </script>
  </body>
</html>

Посмотреть пример
Нужны ли подобные статьи на хабре?

Проголосовало 769 человек. Воздержалось 70 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Alexander Shpak @shpaker
карма
26,5
рейтинг 0,0
Дворник
Похожие публикации

Комментарии (22)

  • +7
    Эцсамое, в подобных статьях принято приводя примеры размещать где-нибудь демку, чтобы посмотреть прямо в браузере, ничего никуда не копипастя.
    • 0
      А я специально не размещаю, чтоб народ сам что-то делал
      • +8
        Ну, без демок «на посмотреть» интереса меньше, это давно замечено. Но дело ваше.
        • +1
          За весь пост тут один более или менее интересный пример и ссылка на него присутствует
          • +1
            Все ссылки нерабочие.
            • 0
              Ок, в ближайшие часы исправлю
            • 0
              Всё, сделал дело. Вообще пришлось весь пост перекроить потому что Хабр отказывался его нормально сохранять в том виде котором он был. Ссылки на фидлы поставил.
              1. [habr] Canvas: images. Example #1, #2, #3
              2. [habr] Canvas: images. Example #4
              • 0
                Спасибо.
      • +1
        Как правило ни кто ничего не делает «сам» до реального проекта. А видеть в чем преимущество технологии перед альтернативами на конкретном примере всегда приятно. Не школа ведь, врятли даже один читатель после поста сразу же начнет «пробовать».
        А за пост огромное спасибо, читал и прошлый пост.
        • 0
          Согласен. Я и сам не вникал пока в голову не пришла кое какая идея и я её не начал реализовывать. А пробовать-не пробовать дело каждого. Кто захочет тот попробует, кто не хочет пробовать — тот и вникать в текст не будет
    • +2
  • 0
    Собственно, собственно, собственно, собственно, собственно, собственно :)
    • 0
      Сейчас исправлюсь )
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Мне кажется что просто в конце (или начале) нужно ставить ссылку на пример. Мне вот не хочется читать полностью статью, а код посмотреть очень интересно. Для этого пришлось искать по тексту ссылочку на ваш пример.
  • +2
    Ну раз народ требует то сейчас расставлю ссылки на примеры
    • +1
      Спасибо за ссылки. Сразу видно, что именно представляет каждый код.
    • 0
      Спасибо, так гораздо удобнее
      • 0
        Вам спасибо что читали ))
  • +1
    Может я чего не понимаю, но по-моему вы чуть загнули с картой. Достаточно было просто матрицы с индексами текстурок, т.е. структуры вроде
    [
    [1, 1, 1, 1, 1],
    [1, 2, 3, 4, 1],
    [1, 5, 5, 5, 1],

    ];

    Так куда нагляднее, да и редактировать не в пример проще.
    • 0
      Просто по моему скромному мнению человеку более понятно и наглядно когда картинка представлена в виде таблицы где есть соответствующие столбцы (координата х) и строки (координаты у). Хотя да можно было проиндексировать эдементы от 1 до 12, а при отрисовке всё равно пришлось бы вычислять две координаты которые бы описывали местоположение нужного кусочка в большой картинке, так что не много избежали математику которая бы не всем была бы сразу понятно. В примере из предыдущего поста я кстати делал карту именно массивом индексамов, но там спрайты рисовались не фрагментами.
  • 0
    Вы вручную прорисовывали тайл по всему холсту, это верно. От себя хочу добавить, что ещё есть возможность сделать то же самое через метод createPattern, получится тот же результат. С одним только «но»: если вы захотите размножить паттерн только по одной из осей (ну что-то типа
    ctx.createPattern(this, 'repeat-x');
    например), то это не будет работать в Firefox. Я тоже на практике пришёл к выводу, что приходится рисовать вручную повторяющийся фон.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.