Pull to refresh

Еще одно Canvas руководство [4]: The end

Reading time5 min
Views19K

В этой части


[Трансформации,
Композиции,
Анимации,
Манипуляция пикселями]

Трансформации


Вспомним некоторые методы


Во первых нам понадобится вспомнить два метода которые были упомянуты в прошлой главе.
save — сохраняет текущее состояние на вершине стека
restore — устанавливает состояние с вершины стека
Под состоянием стоит понимать: матрицу трансформации, область рисования, и следующие свойства: strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline.

Сместим


translate(float x, float y)
— сместим текущее положение начала координат на x и y от предыдущего положения.
Рассмотрим следующий пример:
ctx.save() //Сохраним состояние
ctx.translate(100,100)  //Сместим начало координат
ctx.textBaseline = 'top'
ctx.font = "bold italic 30px sans-serif"
ctx.strokeText("Привет Хабр",0,0) //Казалось бы рисуем в точке (0;0), а текст рисуется не в угле холста
ctx.restore()  //Восстановим состояние, на самом деле не обязательно, но лучше восстановить

Повернем



rotate(float angle)
— повернет оси абсцисс и ординат на угол angle, также не стоит забывать что угол измеряется в радианах, чтобы преобразовать угол в радианы из градусов нужно умножить градусы на Pi и разделит на 180, то есть: rad = deg * Math.PI // 180
Для примера добавим после ctx.translate(100,100) строку: ctx.rotate(45*Math.PI/180)

Уменьшим/увеличим



scale(float x, float y)
— применит новый масштаб к осям x и y.
Для примера заменим ctx.rotate(45*Math.PI/180) на ctx.scale(0.65,1.5)

Перемножим матрицу


transform(float m11, float m12, float m21, float m22, float dx
, float dy)
— перемножит старую матрицу на новую матрицу.

Применим матрицу



setTransform(float m11, float m12, float m21, float m22, float dx
, float dy)
— применит новую матрицу, для примера посмотрим на следующий код.
var img = new Image();
img.onload = function(){
    ctx.save()
    ctx.setTransform(1,0.5,0,1,1,1)
    ctx.drawImage(img,0,0,78,50)
    ctx.restore()
}
img.src = 'brick.jpg';

Я использовал картинку кирпичной стены и shearing трансформацию.
Кроме того о трансформациях можно почитать более подробно в топике от Nutochka

Композиции


Виды операции композиции



За тип операции композиции отвечает свойство globalCompositeOperation, можно было бы расписать все его возможные значения, но есть Canvas Cheat Sheet и лучше показать часть картинки с него, синий прямоугольник был нарисован первым, а красный круг вторым и в зависимости от значения globalCompositeOperation мы получим разные результаты.














Свойство globalAlpha



Свойство globalAlpha — фактически является множителем на которые умножается прозрачность каждого изображения, при этом только в том случае если 0 <= globalAlpha <= 1. Для примера рассмотрим код:
ctx.globalAlpha = 0.6;
ctx.fillRect(0,0,100,100)
ctx.fillRect(50,50,100,100)


Анимация


requestAnimationFrame


Если раньше для анимации использовались таймеры то теперь появилась специальная функция requestAnimationFrame, тема уже освещенная так что лучше прочитать хабраперевод от azproduction.

Основной принцип анимации


Основной принцип анимации это стереть старый кадр и нарисовать новый. Чтобы стереть старый кадр легче всего воспользоваться функцией clearRect. Для примера рассмотрим следующий код, для начала добавьте определение requestAnimationFrame в ваш код:
var x = 400,
    y = 300;
(function loop(){
    ctx.clearRect(0,0,800,600);
    ctx.beginPath();
    ctx.arc(x++,y++,50,0,Math.PI*2,false);
    ctx.fill()
    requestAnimFrame(loop);
})();

Также об анимации можно почитать в топике от trickii

1 игра != 1 canvas


Это правило я увидел в презентации про ускорение canvas и понял что это самое простое правило.
Вместо того чтобы рисовать сложные изображения при каждом кадре можно сохранить это изображение в виртуальный canvas и затем рисовать его при каждом кадре, но однако стоит заметить что не надо всё пихать в виртуальный canvas, стоит пихать только действительно сложные для рисования фигуры (например текст или градиент). Для примера я сделал небольшой тест на jsperf.com, не идеал, но думаю смысл понятен.

Манипулируем пикселями


В canvas кроме высокоуровневых методов присутствует еще и полноценный доступ к пикселям, с помощью него можно творить замечательный вещи (я имею ввиду фильтры и подобное), а также рисовать простые вещи очень быстро. Для работы с пикселями существует специальный тип ImageData, он обладает тремя свойствам width,height (думаю не нуждаются в объяснении) и data — массив значений r,g,b,a для каждого пикселя.

Как хранятся данные о пикселях



Данные о пикселях как говорилось ранее хранятся в массиве, при этом количество элементов в массиве равно количеству пикселей умноженному на 4. Все элементы принимают значения в диапазоне 0..255.
В том числе alpha канал хранится в значениях от 0..255. На картинке показано что четыре значения r,g,b,a формируют сегмент, сегмент в свою очередь является единой неделимой ячейкой хранящей параметры одного пикселя.

Создание пустого ImageData


ImageData = context.createImageData(int w, int h)
— создаст пустой ImageData с шириной и высотой w и h соответственно.
ImageData = context.createImageData(imageData)
— создаст пустой ImageData с шириной и высотой переданного imageData, заметьте: данные не копируются.

Применение ImageData


context.putImageData(imageData imgd, int x, int y)
— изменит массив пикселей canvas в области из точки x y с шириной и высотой imageData.

Простой пример


Но хватит теории, приступим к делу давайте попробуем нарисовать график функции y = x*x, без сглаживания:
var imgd = ctx.createImageData(800, 600);
	
var x,y,segment,xround,yround;
	
for (x=-40 ; x<=40 ; x+=0.01) {
	y = x*x;
		
	xround = ~~(x+0.5);
	yround = ~~(y+0.5);
		
	segment = ((-yround+400)*imgd.width + xround + 40)*4;
		
	imgd.data[segment+3] = 255;
}
	
ctx.putImageData(imgd, 0, 0);

Для сравнения производительности я написал подобный код с использованием fillRect, опять на jsperf, однако чтобы нарисовать одну точку лучше использовать fillRect так как создание imageData тоже отнимает ресурсы.

Манипуляция пикселями изображения


ImageData = context.createImageData(int x, int y, int w, int h)
— вернет объект imageData с данными из canvas в прямоугольной области с левой верхней вершиной в (x;y) и шириной, высотой w и h соответственно. Для примера попробуем выделить на фотографии участки белого цвета:
var img = new Image();
img.src = 'test.jpg';  //Или что там у вас

img.onload = function(){
	//netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");  -- раскомментируйте если запускаете код в FireFox из локалки
	ctx.drawImage(img, 0, 0, 160, 120);

	var imgd = ctx.getImageData(0, 0, 160, 120); //Получаем imageData

	for (var i=0 ; i<imgd.data.length ; i+=4) {
		imgd.data[i] = imgd.data[i+1] = imgd.data[i+2] = imgd.data[i] < 200 && imgd.data[i+1] < 200 && imgd.data[i+2] < 200 ? 0 : 255; //Проверяем белый пиксель или нет
	}

	ctx.putImageData(imgd, 0, 0);  //Фух, проверили, теперь вставляем новые данные
}
Tags:
Hubs:
Total votes 31: ↑30 and ↓1+29
Comments4

Articles