Тег html canvas. Часть 2

September 10, 2007Comments Off on Тег html canvas. Часть 2

Этой статьей я продолжу серию материалов посвященных тегу canvas – специальному расширению, поддерживаемому передовыми браузерами (opera, firefox) и позволяющему вам, используя вызовы функций javascript, рисовать растровые изображения в окне браузера.

В прошлый раз я рассказал об методике внедрения тега canvas внутрь веб-страницы, о том как сделать fallback – механизм позволяющий корректно обработать ситуацию когда страница открывается не поддерживающим возможности canvas браузером (это про тебя дорогой internet explorer). Так же мы рисовали простые фигуры: линии, прямоугольники, эллипсы и дуги. Сегодня мы поговорим о работе с преобразованиями, управлении режимами наложения изображений при отрисовке.

Что такое преобразования? В мире компьютерной графики под ними понимают простейшие математические операции, позволяющие вам перед началом, собственно, рисования выполнить некоторое изменение в системе координат холста. По умолчанию ваш холст имеет точку начала координат (0,0), а масштаб оперирует минимальной цифрой в 1 пиксель, оси же направлены строго под прямым углом друг к другу – одним словом, то, что мы называем прямоугольной декартовой системой координат.

И, соответственно, перед началом рисования вы можете изменить эту систему координат, использую вызовы специальных функций в составе контекста рисования. За это отвечают следующие функции. Translate – эта функция получает в качестве параметра координаты куда переместится центр координат (СК). Функция rotate позволяет выполнить вращение СК. Вращение выполняется против часовой стрелки. Последняя функция преобразования ск – scale - масштабирование. Эта функция получает два параметра x,y, которые будут играть роль коэффициентов масштабирования. Очевидно, что если вы рисуете что-то достаточно сложное, то вы будете многократно вызывать фукцнии изменения свойств СК: вы будете менять параметры, а затем возвращаться в некоторое исходное состояние. Чтобы не запутаться какие свойства у вас сейчас установлены (на самом деле вы можете изменять гораздо большее количество параметров управляющих особенностями рисования, чем я перечислил выше). Так вот, чтобы не забыть какие свойства установлены для canvas, вы можете применять две парные функции: save и restore. Эти функции не получают ни каких параметров. Функция save сохраняет текущее состояние canvas – все его свойства. А функция restore – наоборот, восстанавливает эти параметры. Работа функций сохранить/восстановить построена по принципу стека. Вы можете вызывать фукнцию save любое количество раз и все состояния будут сохраняться укладываясь в некую виртуальную стопку. Каждый же вызов restore будет брать с вершины этой стопки сохраненный набор параметров. Соответственно, вы не можете вызывать restore большее количество раз, чем было положено туда состояний с помощью save.

Теперь давайте сделаем парочку примеров на каждую из этих функций. Начнем с перемещения центра СК. Для пример нарисуем шахматную доску состоящую из множества черных квадратиков на белом фоне. Каждый квадрат будет рисоваться размером в 10px, и иметь координаты 0,0. естественно, чтобы квадратики не накладывались друг на друга я вынужден перед рисованием каждого из них передвигать центр СК.
  1. <html>
  2.    <head>
  3.    <!-именно здесь будут размещаться функции, которые рисуют на canvas-->
  4.   <script language="javascript">
  5.   function draw (){
  6.     var canvas = document.getElementById('canva');
  7.         if (!canvas.getContext) return;
  8.  
  9.         var ctx = canvas.getContext('2d');
  10.  
  11.         // заливаем доску белым цветом
  12.         ctx.fillStyle = "rgb(255,255,255)";
  13.         ctx.fillRect (0, 0, 400, 400);
  14.  
  15.         for (var i = 0; i < 8; i++)
  16.           for (var j = 0; j < 8; j++){
  17.               if ( (i +j ) % 2 == 0) continue;
  18.  
  19.               ctx.save ();// сохраняем контекст рисования
  20. 	      ctx.translate (i*50 , j*50);// смещаем центр СК
  21.               ctx.fillStyle = "rgb(0,0,0)";
  22.               ctx.fillRect (0, 0, 50, 50);
  23.  
  24.               ctx.restore ();// восстанавливаем свойства контекста рисования
  25.           } 
  26.   }
  27.   </script>
  28.  
  29. </head>
  30. <body>
  31.   <canvas id="canva" width="400" height="400" style="border: 1px solid black;">
  32.     <img src="no_canvas_sorry.jpg" />
  33.   </canvas>
  34.   <br />
  35.   <button onclick="draw()">draw!</button><!-эта кнопка вызывает некоторую функцию рисования -->
  36. </body>
  37.  
  38. </html>
Результат работы показан на рис. 1.



Для задачи демонстрации преобразования вращения и масштабирования я решил выбрать единый пример – спиральный квадрат. Это изображение множества вложенных друг в друга квадратов, каждый из которых на очередном шаге вложенности уменьшается в размере и вращается вокруг своей оси не некоторый угол. Основу формул я взял с сайта http://fractalworld.xaoc.ru/article/kineskop.html. И код у меня (благодаря преобразованиям) получился более компактным. Вкратце идея в том, что до начала цикла я смещаю центр координат в точку (200,200) – геометрический центр области canvas. Затем в цикле я рисую прямоугольник из точки (-200,-200) и размером (400,400). Эффект вращения и ухода в даль достигается за счет того что предварительно я вращаю ось на небольшой угол PI/20, а также уменьшаю коэффициент масштабирования. Результат работы показан на рис. 2.



Внимание: в случае множественных последовательных операций вращения, масштабирования и смещения эффект накапливается.
  1. <html>
  2.    <head>
  3.    <!-именно здесь будут размещаться функции, которые рисуют на canvas-->
  4.   <script>
  5.   function draw (){
  6.     var canvas = document.getElementById('canva');
  7.         if (!canvas.getContext) return;
  8.  
  9.         var ctx = canvas.getContext('2d');
  10.  
  11.         // заливаем доску белым цветом
  12.         ctx.fillStyle = "rgb(255,255,255)";
  13.         ctx.fillRect (0, 0, 400, 400);
  14.  
  15.         var DELTA_ANGLE = Math.PI / 20;
  16.         ctx.translate (200,200);
  17.  
  18.         for (var i = 1; i < 21; i++){
  19. 	      ctx.rotate (DELTA_ANGLE);
  20. 	      var coeff = Math.sin(Math.PI/4) / Math.sin(3*Math.PI/4 - DELTA_ANGLE);
  21.               ctx.scale (coeff,coeff);
  22. 	      ctx.strokeRect(-200,-200, 400,400);
  23.        } 
  24.  
  25.   }
  26.   </script>
  27.  
  28. </head>
  29. <body>
  30.   <canvas id="canva" width="400" height="400" style="border: 1px solid black;">
  31.     <img src="no_canvas_sorry.jpg" />
  32.   </canvas>
  33.   <br />
  34.   <button onclick="draw()">draw!</button><!-эта кнопка вызывает некоторую функцию рисования -->
  35. </body>
  36.  
  37. </html>
Теперь, как я и обещал, переходим к работе с внешними изображениями. Вы можете взять существующую картинку, загрузить ее и отрисовать на canvas. Существует несколько версий функции drawImage отличающихся параметрами. Так в простейшем случае достаточно указать:
  1. drawImage(image, x, y)
В качестве ее параметров следует указать ссылку на объект изображения, затем координаты где будет располагаться левый верхний угол копируемой картинки.

Возможно при отрисовке изображения выполнить ее масштабирование – в этом случае используйте функцию
  1. drawImage(image, x, y, width, height)
Здесь добавились два новых параметра – размер рисуемого изображения. И это еще не все: есть версия функции с еще четырьмя дополнительными параметрами:
  1. drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
С их помощью можно указать какой фрагмент исходного изображения должен быть вырезан для последующей отрисовки (его координаты и размеры). Обратите внимание на то что параметры располагаются уже в несколько ином порядке: первыми идут координаты и размеры области вырезаемой из исходной картинки и только затем мы указываем координаты и размеры области куда это изображение будет помещено.

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



Пример кода:
  1. <html>
  2.    <head>
  3.    <!-именно здесь будут размещаться функции, которые рисуют на canvas-->
  4.   <script>
  5.   function draw (){
  6.     var canvas = document.getElementById('canva');
  7.         if (!canvas.getContext) return;
  8.  
  9.         var ctx = canvas.getContext('2d');
  10.  
  11.         // заливаем доску белым цветом
  12.         ctx.fillStyle = "rgb(255,255,255)";
  13.         ctx.fillRect (0, 0, 400, 400);
  14.  
  15.         var img_fox = document.getElementById ('img_fox');             
  16.         //получаем ссылку на изображение
  17.         if (! img_fox){
  18.          // выполняем проверку того, что изображение действительно было найдено
  19.          alert ('изображение не найдено');
  20.          return;
  21.         }  
  22.         // рисуем изображение в его естественном масштабе
  23.         ctx.drawImage (img_fox , 0, 0);
  24.  
  25.         // а теперь рисуем изображение немного в стороне и в уменьшенном размере
  26.         ctx.drawImage (img_fox , 100, 250, 100, 75);
  27.  
  28.         // последний прием, когда я вырезаю из изображения некоторый фрагмент
  29.         ctx.drawImage (img_fox , 100, 0, 80, 100, 250,50, 80, 100);
  30.  
  31.   }
  32.   </script>
  33.  
  34. </head>
  35. <body>
  36.   <canvas id="canva" width="400" height="400" style="border: 1px solid black;">
  37.     <img src="no_canvas_sorry.jpg" />
  38.   </canvas>
  39.   <br />
  40.   <button onclick="draw()">draw!</button><!-эта кнопка вызывает некоторую функцию рисования -->
  41.  
  42.   <img src="foxes_008.jpg"  id="img_fox" />
  43. </body>
  44.  
  45. </html>
А в этом примере кода я в отличие от предыдущего варианта (тогда изображение бралось уже встроенное внутрь страницы) загружаю оригинальную картинку динамически.
  1. <html>
  2.    <head>
  3.    <!-именно здесь будут размещаться функции, которые рисуют на canvas-->
  4.   <script>
  5.   function draw (){
  6.     var canvas = document.getElementById('canva');
  7.         if (!canvas.getContext) return;
  8.  
  9.         var ctx = canvas.getContext('2d');
  10.  
  11.         // заливаем доску белым цветом
  12.         ctx.fillStyle = "rgb(255,255,255)";
  13.         ctx.fillRect (0, 0, 400, 400);
  14.  
  15.  
  16.         var img_fox = new Image ();
  17.         // привязываем обработчик события - загрузка картинки завершена
  18.         img_fox.onload = function () {
  19.           // рисуем изображение в его естественном масштабе
  20.          ctx.drawImage (this , 0, 0);
  21.          // а теперь рисуем изображение немного в стороне и в уменьшенном размере
  22.          ctx.drawImage (this , 100, 250, 100, 75);
  23.          // последний прием, когда я вырезаю из изображения некоторый фрагмент
  24.          ctx.drawImage (this , 100, 0, 80, 100, 250,50, 80, 100);
  25.         };
  26.         img_fox.src = 'foxes_008.jpg';
  27.   }
  28.   </script>
  29.  
  30. </head>
  31. <body>
  32.   <canvas id="canva" width="400" height="400" style="border: 1px solid black;">
  33.     <img src="no_canvas_sorry.jpg" />
  34.   </canvas>
  35.   <br />
  36.   <button onclick="draw()">draw!</button><!-эта кнопка вызывает некоторую функцию рисования -->
  37.  
  38.  
  39. </body>
  40.  
  41. </html>
Интересно, что вместо изображения можно ссылаться на другой элемент canvas. Также существует нестандартное расширение (читай, поддерживаемое только mozilla firefox) которое умеет рисовать на canvas содержимое некоторого окна (например, плавающего фрейма). Работает данная функция не всегда, а только для исполняющихся в среде chrome приложений (читай, обычная веб-страница так делать не умеет – только расширения/плагины для firefox).

Следующая наша подтема - комбинирование изображений. Идея в том, что когда вы рисуете на canvas некоторое изображение, возникает вопрос как оно должно взаимодействовать с тем изображением, что уже есть. У объекта canvas есть специальное свойство: globalCompositeOperation . Ему можно присваивать набор предопределенных значений. Следующую таблицу я аккуратно позаимствовал на сайте http://developer.mozilla.org/en/docs/Canvas_tutorial. Я решил, что не стоит коверкать эту прекрасную таблицу своими заумствованиями и просто перевел текст. В общем, смотрите ниже:

Таблица-справочник возможных значений переменной globalCompositeOperation с примерами.


source-over (default) Это режим по-умолчанию когда рисуется новая картинка поверху старой. destination-over Новое изображение рисуется под старым
source-in Новое изображение рисуется только в тех местах, где старое и новое изображение пересекаются. Все остальное становится прозрачным. destination-in Существующее изображение остается только в тех местах, где старое и новое изображение пересекаются. Все остальное становится прозрачным.
source-out Новое изображение рисуется только в тех местах, где оно не пересекается со старым изображением. destination-out Старое изображение сохраняется в тех местах, где оно не пересекается с новым изображением.
source-atop Новое изображение рисуется только в тех местах где оно пересекается с уже существующим изображением на canvas. destination-atop Существующее изображение остается только в тех местах, где оно пересекается с новым изображением. Новая картинка рисуется позади старого содержимого.
lighter В тех местах где старое и новое изображение пересекаются идет вычисление нового цвета как суммы двух цветов картинок участников. darker В тех местах где старое и новое изображение пересекаются идет вычисление нового цвета как разницы между двумя цветами картинок участников.
xor Изображение становится прозрачным в том месте где происходит пересечение старой и новой картинок. В остальных же местах изображение рисуется как обычно. copy Рисуется новое изображение, а все что было до этого убирается.