Трансформации SVG-графики

Трансформации SVG-графики

Преобразования (трансформации) плоских фигур на плоскости в SVG выполняются с помощью атрибута transform="matrix(a,b,c,d,e,g)" тегов, задающих видимые объекты. В SVG атрибут transform может принимать строковые значения, указывающие на то или иное элементарное преобразование. Например, transform="scale(2,2)" задает увеличение масштаба графического объекта в 2 раза. Однако все предопределенные частные преобразования и множество других могут быть выполнены с помощью универсального атрибута transform="matrix(a,b,c,d,e,g)" тега, задающего объект трансформации (например, <image>). Так, увеличение масштаба в 2 раза может быть задано как transform="matrix(2,0,0,2,0,0)".
С математической точки зрения преобразование на плоскости задается матрицей вида:
   |a c e|
   |b d g|
   |0 0 1|
Третья строка матрицы для двумерных преобразований всегда одна и та же: 0 0 1. Поэтому ее не учитывают при задании преобразования посредством атрибута вида transform="matrix(a,b,c,d,e,g)" (только 6, а не 9 параметров).

Примеры элементарных трансформаций:

translate (перемещение на x и y px)		 — matrix(1,0,0,1,x,y)
rotate (поворот на угол a)			 — matrix(cos(a),sin(a),-sin(a),cos(a),0,0)
scale (масштабирование с коэффициентами kx и ky) — matrix(kx,0,0,ky,0,0)
skewX (наклон вдоль X на угол a)                 — matrix(1,0,tg(a),1,0,0)
skewY (наклон вдоль Y на угол a)                 — matrix(1,tg(a),0,1,0,0)
тождественное преобразование,
которое ничего не изменяет                       — matrix(1,0,0,1,0,0)

Сложное преобразование получается как последовательность элементарных преобразований. Оно описывается матрицей, являющейся результатом произведения матриц, соответствующих элементарным преобразованиям. Матричная запись преобразований компактна, но с трудом поддается интуиции или семантической интерпретации. Вместе с тем, многие типичные преобразования можно понять, выполнив экспериментально произведение нескольких элементарных преобразований подряд и заметив, как формируется результирующая матрица относительно исходной. Так например, легко заметить, что:

Однако не всегда сложное преобразование получается элементарным замещением оставшихся "нулей" в первой матрице "соответствующими" значениями из второй. Произведение матриц — определенная в математике алгебраическая операция, котрую лучше выполнить по всем правилам, а не полагаясь на интуицию.
Нередко требуется выполнить сложные преобразования, состоящие из уже освоенных более элементарных. Более того, часто требуется выполнить преобразование над текущим состоянием объекта, а не над исходным. Однако задание нового значения атрибута transform приведет к выполнению соответствующего преобразования исходного, а не текущего состояния изображения. Поэтому если вам требуется организовать последовательные переходы от одного состояния к другому, придется их пересчитывать относительно исходного. Для этого я и предлагаю две функции на JavaScript — pmatrix() и pmatrix2(). Каждая из них возвращает результат произведения двух матриц в виде строки, пригодной в качестве значения атрибута transform. Обе функции делают одно и тоже, но по-разному. Мне кажется, что вторая из них лучше. Впрочем, вы можете попытаться написать для означенной цели что-нибудь более эффективное.

function pmatrix(a1,b1,c1,d1,e1,g1,a2,b2,c2,d2,e2,g2){
/* Параметры: значения параметров 1-го и 2-го преобразований
   Возвращает строку вида "matrix(a,b,c,d,e,g)",
   являющуюся результатом произведения двух матриц
*/
return "matrix("+(a1*a2+c1*b2)+","+
      (b1*a2+d1*b2)+","+
      (a1*c2+c1*d2)+","+
      (b1*c2+d1*d2)+","+
      (a1*e2+c1*g2+e1)+","+
      (b1*e2+d1*g2+g1)+")"
}

function pmatrix2(s1,s2){
/*Параметры: строки вида "matrix(a,b,c,d,e,g)"
  Возвращает строку вида "matrix(a,b,c,d,e,g)",
  являющуюся результатом произведения двух матриц
*/
var sep;// разделитель ,бывают "," и пробел
if (s1.indexOf(",")==-1) {sep=' '} else {sep="," };
var l=s1.length
var s1=s1.substring(7,s1.length-1); // содержимое скобок
s1=s1.split(","); 			// делаем массив
var s2=s2.substring(7,s2.length-1); // содержимое скобок
if (s2.indexOf(",")==-1) {sep=" "} else {sep=","};
s2=s2.split(",");				// делаем массив

return "matrix("+(s1[0]*s2[0]+s1[2]*s2[1])+","+
      (s1[1]*s2[0]+s1[3]*s2[1])+","+
      (s1[0]*s2[2]+s1[2]*s2[3])+","+
      (s1[1]*s2[2]+s1[3]*s2[3])+","+
      (s1[0]*s2[4]+s1[2]*s2[5]+s1[4]*1)+","+
      (s1[1]*s2[4]+s1[3]*s2[5]+s1[5]*1)+")";
}
Далее в примерах я буду использовать функцию pmatrix2(), как наиболее удобную для работы в SVG, когда необходимо с помощью скрипта изменять текущее положение объекта. Обратите внимание на возню с разделителем в коде этой функции. Дело в том, что параметры в matrix() можно разделять запятой или пробелом. Однако при установке атрибута transform="matrix(...)" некоторые браузеры (Opera, Safari, Chrome и IE) сохраняют его, используя пробелы при разделении параметров. Firefox сохраняет так,как записано.

Примеры