учебники, программирование, основы, введение в,

 

Программное рисование во Flash MX

Предыдущие версии Флэш (вплоть до Флэш 5) не имели функций программного рисования. В чем-то такое решение было оправдано: ведь Флэш предназначен для рисования в design-time, то есть вручную - до запуска анимации. И тем не менее возникали задачи (часто не такие уж сложные), которые требовали именно программного рисования. Это и эффекты, для которых потребовалось бы слишком много ручной работы (например, градиент, изменяющийся в каждом следующем кадре строго определенным образом, или набор из огромного количества определенным образом расположенных тонких линий). И, тем более, - изображения, внешний вид которых сильно зависит от действий пользователя и заранее совершенно неизвестен (например, графики функций). Флэш МХ, наконец-то, предоставляет нам все необходимые средства для решения этих задач. И начнем мы изучение этих средств с самого простого - прямых и кривых различного вида.
Прямые и кривые
Итак, внутрь любого клипа мы можем добавить программно нарисованные прямые или кривые. Заметьте, что рисование происходит именно в конкретном клипе (и будет сдвигаться вместе с клипом или перекрываться вышележащими клипами). Приятное свойство Флэша - это антиалиасинг проводимых линий. Больше никаких "лесенок"!
Методы программного рисования прямых во Флэше мало чем отличаются от таковых в других языках программирования. Поэтому начнем сразу с примера. Вот как выглядит код, рисующий равносторонний треугольник.
//создаем MovieClip, в котором хотим нарисовать треугольник
_root.createEmptyMovieClip("triang_mc", 1);
thickness = 1; //толщина линии
lineColor = 0xff0000; //цвет линии
alpha = 100; //прозрачность линии
//далее, передаем эти аргументы в метод, определяющий стиль линии
triang_mc.lineStyle(thickness, lineColor, alpha);
//далее, определяем координаты вершин
Ax = 20; Ay = 100;
Bx = 120; By = 100;
Cx = 70; Cy = 100*(1-Math.sqrt(3)/2);
//и, наконец, собственно рисуем треугольник
triang_mc.moveTo(Ax, Ay); //ставим курсор в вершину A
//соединяем вершины
triang_mc.lineTo(Bx, By);
triang_mc.lineTo(Cx, Cy);
triang_mc.lineTo(Ax, Ay);

Обратите внимание, что "холстом" для программно нарисованных объектов является MovieClip (в данном случае triang_mc). Если в клипе есть объекты, вставленные в design-time, то они будут отображаться поверх нарисованных через ActionScript. Последние будут перемещаться и поворачиваться, сжиматься и растягиваться, менять прозрачность и цвет вместе с клипом, а также влиять на _width и _height.
На _width и _height хотелось бы остановиться подробнее. В редакторе FlashMX с помощью панелей Properties или Info мы можем достаточно точно установить размеры объектов. И в праве ожидать, что те же значения мы получим, спрашивая у объекта значения _width и _height в ActionScript. К сожалению, это не всегда так. Например, создайте клип, содержащий одну горизонтальную линию длиной 200 пикселей, толщиной 10. В панели Properties или Info вы увидите, что ширина этого клипа ровно 200.0 пикселей, а высота - 0.. В размерах, которые вы задаете в редакторе, толщина линий не учитывается. То же самое можете проделать программно. А теперь давайте посмотрим, как это выглядит в run-time. Вы видите разницу между двумя черными линиями, одна из которых нарисована в design-time, другая - через ActionScript? Я тоже не вижу. А вот flash-player видит. При измерении свойств _width и _height учитывается конечная толщина линий. При этом для объектов, вставленных в design-time и для нарисованных в ActionScript, учитывается по-разному (см. окно Output на. Во втором случае к "номинальным" размерам прибавляется с обеих сторон толщина линии, в первом - половина толщины линии. Так что это необходимо учитывать, если нужно знать точное соответствие размеров объекта в design-time и в run-time (для чего это может понадобиться - см., например, гл. 11). Однако никто не может поручиться, что абсолютно все плееры ведут себя в этом отношении одинаково (а особенно - что так же будут вести себя новые версии плеера). Гораздо более надежно в этих случаях пользоваться формами, или, что то же самое, заливками (shapes). Об этом читайте ниже.
В использовались методы lineStyle, moveTo и lineTo. Если добавить к ним еще метод clear (который, во-первых, стирает все программно нарисованное, а также сбрасывает настройки, заданные методом lineStyle), то этого будет уже достаточно, чтобы рисовать довольно сложные объекты. Такие, как проекция гиперболоида вращения (), или бегающая ломаная ), парабола ) или спираль ().
linesNum = 150;
lineRot = 0.35;
xCenter = 275;
HalfWidth = 250;
MinY = 0;
MaxY = 400;
for (var i = 0; i < linesNum; i++){
x1 = xCenter + HalfWidth*Math.sin(2*Math.PI * i / linesNum);
x2 = xCenter + HalfWidth*Math.sin(2*Math.PI *
(i + lineRot*linesNum) / linesNum );
hyper_mc.moveTo(x1, MinY);
hyper_mc.lineTo(x2, MaxY);
}
pointsNum = 6; avSpeed = 20;
globalWidth = 550;
globalHeight = 400;
_root.createEmptyMovieClip("lines_mc", 1);
points = new Array();
for (var i = 0; i < pointsNum; i++){
points.push({x: Math.random()*globalWidth,
y: Math.random()*globalHeight,
vx: 2*avSpeed*(Math.random() - 0.5),
vy: 2*avSpeed*(Math.random() - 0.5)});
}

lines_mc.onEnterFrame = function(){
for (var i = 0; i < points.length;i++){
points[i].x += points[i].vx;
points[i].y += points[i].vy;
xbound = null;
if (points[i].x < 0) xbound = 0;
if (points[i].x > globalWidth) xbound = globalWidth;
if (xbound != null){
points[i].x = xbound;
points[i].vx = -points[i].vx
}
ybound = null;
if (points[i].y < 0) ybound = 0;
if (points[i].y > globalHeight) ybound = globalHeight;
if (ybound != null){
points[i].y = ybound;
points[i].vy = -points[i].vy
}
}
this.clear();
this.lineStyle(1, 0xff0000, 100);
this.moveTo(points[i-1].x, points[i-1].y);
for (var i=0; i< points.length; i++)
this.lineTo(points[i].x, points[i].y);
}

_root.createEmptyMovieClip("curve_mc", 2);
curve_mc.lineStyle(1, 0x00cc00, 100);
var xControl = 50;
var yControl = 100;
var xTarget = 70;
var yTarget = 30;
for (var tau=0; tau <=1; tau+=0.01)
curve_mc.lineTo(2*xControl*tau*(1-tau) + xTarget*tau*tau,
2*yControl*tau*(1-tau) + yTarget*tau*tau);

_root.createEmptyMovieClip("spir_mc", 1);
spir_mc._x = 275; spir_mc._y = 200;
spir_mc.lineStyle(2, 0xcc00ff, 100);
for (var t=0; t<500; t++){
spir_mc.lineTo(0.25*t*Math.cos(0.1*t), 0.25*t*Math.sin(0.1*t));
}
spir_mc.moveTo(0, 0);
for (var t=0; t<500; t++){
spir_mc.lineTo(-0.25*t*Math.cos(0.1*t), -0.25*t*Math.sin(0.1*t));
}
Однако, кроме этих вышеперечисленных, для рисования линий во Флэш МХ существует еще один оператор: curveTo. Давайте разберемся, для чего он может понадобиться. Из References, да и просто из названия оператора, можно понять, что он нужен для рисования плавных кривых. Но так ли уж необходимо заводить для этого специальный оператор? Ведь, как видно из, плавные кривые можно рисовать, пользуясь только lineTo.
Однако давайте посмотрим, что будет, если мы немного модифицируем код ви заставим спираль вращаться. Для этого процесс рисования спирали перенесем в метод onEnterFrame, причем каждый раз будем поворачивать ее на небольшой угол ().
spir_mc.onEnterFrame = function(){
this.clear();
this.lineStyle(2, 0xcc00ff, 100);
if (curfr == undefined) curfr = 0
else curfr++;
for (var t=0; t<500; t++){
this.lineTo(0.25*t*Math.cos(0.1*t + 0.1*curfr),
0.25*t*Math.sin(0.1*t + 0.1*curfr));
}
spir_mc.moveTo(0, 0);
for (var t=0; t<500; t++){
this.lineTo(-0.25*t*Math.cos(0.1*t + 0.1*curfr),
0.25*t*Math.sin(0.1*t + 0.1*curfr));
}
}

Полюбуйтесь на вращающуюся спираль. У вас не закружилась голова? Тогда посмотрите на загрузку процессора... Дело в том, что в данном примере в каждом кадре тысячу раз вызывается оператор lineTo, следовательно, плееру приходится отображать тысячу отрезков, каждый из которых - отдельный объект.
Конечно, мы выбрали экстремальный случай, когда в каждом кадре нужно перерисовать всю картину. А это требуется не всегда. Так, вмоделируется поведение броуновской частицы, и на экране отображается траектория ее движения. В этом случае в каждом кадре достаточно дорисовать отрезок траектории частицы, пройденный только за этот кадр, а всю картину перерисовывать не нужно. То есть, достаточно только одного оператора lineTo. Может быть, в этом случае загрузка процессора будет приемлемой? Однако, не тут-то было. Вверху (б) показан график зависимости длительности кадра от количества кадров, прошедших с момента запуска симуляции, а под ним (в) - график зависимости загрузки процессора от времени. (Конечно, отображение графика б добавляет в каждый кадр еще один вызов оператора lineTo, но качественно на характер картины это не влияет). Сначала длительность кадра постоянна - 33 миллисекунды (что соответствует номинальной скорости проигрывания 30 кадров в секунду). Но загрузка процессора в это время неуклонно возрастает. И когда она доходит до ста процентов, начинает возрастать длительность кадра. То есть, чем больше линий на экране, тем большее время тратится на перерисовку. Из этого можно сделать только один вывод: даже если картина почти не меняется (добавляется один отрезок в несколько пикселей), флэш-плеер все равно перерисовывает ее полностью.
_root.createEmptyMovieClip("brown", 1);
_root.createEmptyMovieClip("timePlot", 2);
curX = 125; curY = 125;
brown.moveTo(curX, curY);
frame = 0;
timePlot._yscale = -100;
timePlot._y = 150;
timePlot._x = 250;
timePlot.lineStyle(1, 0x0000cc, 100);
time = getTimer();
brown.lineStyle(1, 0x000000, 100);
brown.onEnterFrame = function(){
curX += Math.round((Math.random() - 0.5)*10);
curX = curX > 250 ? 250 : ( curX < 0 ? 0 : curX);
curY += Math.round((Math.random() - 0.5)*10);
curY = curY > 250 ? 250 : ( curY < 0 ? 0 : curY);
brown.lineTo(curX, curY);
frame++;
deltaTime = getTimer() - time;
time = getTimer();
timePlot.lineTo(frame, 2*deltaTime);
if (timePlot._width > 250){
timePlot._xscale /=2;
}
}

Можно ли как-то повлиять на эту ситуацию? В случае- боимся, что никак. Разве что каким-нибудь образом убирать с экрана часть траектории. Однако, это случай экстремальный: действительно, здесь каждый нарисованный отрезок никак не связан с предыдущими, и предсказать его расположение невозможно.
В большинстве же случаев оптимизация все-таки возможна. Например, с помощью оператора curveTo. Давайте несколько модифицируем код из.
_root.createEmptyMovieClip("spir_mc", 1);
_root.createEmptyMovieClip("spirneg_mc", 2);
spir_mc._x = 275; spir_mc._y = 200;
spir_mc.lineStyle(3, 0xcc00ff, 100);
spirneg_mc._x = 275;
spirneg_mc._y = 200;
spirneg_mc.lineStyle(3, 0xcc00ff, 100);
alpha = 5;
dt = 1;
tmax = 50;
sindt = Math.sin(dt);
cosdt = Math.cos(dt);
cost = 1; sint = 0;
for (var t=0; t<tmax; t+=dt){
R1 = t*t*(cosdt - (t + dt)*sindt) - (t + dt)*(t + dt);
R2 = -t*t*(sindt + (t + dt)*cosdt) + t*(t + dt)*(t + dt);
factor = -alpha /( dt*cosdt + (t*(t+dt) + 1)*sindt);
xc = factor*(R1*cost + R2*sint);
yc = factor*(-R2*cost + R1*sint);
cost = Math.cos(t+dt); sint = Math.sin(t+dt);
xt = alpha*(t+dt)*cost; yt = alpha*(t+dt)*sint;
spir_mc.curveTo(xc, yc, xt, yt);
spirneg_mc.curveTo(-xc, -yc, -xt, -yt);
}

В результате выполнения этого кода картинка получается точно такая же, как и в. Однако этот код выполняется в три раза быстрее.
Оператор curveTo позволяет не только ускорить работу программы, но и упростить ее. Так, цикл for из:
for (var tau=0; tau <=1; tau+=0.01)
curve_mc.lineTo(2*xControl*tau*(1-tau) + xTarget*tau*tau,
2*yControl*tau*(1-tau) + yTarget*tau*tau);

может быть заменен одним оператором, как в следующем примере:
curve_mc.curveTo(xControl, yControl, xTarget, yTarget);

Конечно,специально подобран так, чтобы curveTo так на него ложилась. Однако, почти любую гладкую кривую можно достаточно точно аппроксимировать, если грамотно пользоваться оператором curveTo.
В References можно прочитать, что оператор curveTo рисует кривую, используя текущий стиль линии, начиная от текущей позиции и заканчивая в точке (xTarget, yTarget) (имена переменных взяты из), используя контрольную точку (xControl, yControl). Что означает эта контрольная точка, разработчики не конкретизируют, оставляя пользователю простор для догадок. Однако, с помощью несложного исследования можно установить, что curveTo изображает кусок параболы, начинающийся в текущей позиции и заканчивающийся в (xTarget, yTarget), такой что касательные в начальной и конечной точках пересекаются в точке (xControl, yControl).
Это так называемая кривая Безье второго порядка.
Кривая Безье представляет собой гладкую кривую, построенную по конечному набору точек на плоскости. Строится она следующим образом.
Пусть на плоскости заданы три точки P0, P1 и P2. Для определенного значения параметра τ от 0 до 1 выберем на отрезке [P0, P1] точку P01τ, которая делит отрезок в отношении τ/(1-τ), на отрезке [P1, P2] - аналогичную точку P12τ, и на получившемся отрезке [P01τ, P12τ] - точку Pτ, которая в свою очередь делит отрезок [P01τ, P12τ] в отношении τ/(1-τ). Точки Pτ, полученные таким образом для всех значений τ от 0 до 1, и составляют кривую Безье второго порядка.
Нетрудно показать, что кривая P(τ) проходит через точки P0 и P2, касательные к ней в этих точках совпадают с прямыми, на которых лежат отрезки [P0, P1] и [P2, P1], это кривая второго порядка (парабола), аналитически в векторном виде записывается следующим образом:
Способ построения кривой Безье для произвольного числа точек показан на(на примере шести точек P0 - P5). Так же, как и в рассмотренном выше случае трех точек, для каждого значения параметра τ от 0 до 1 найдем соответствующую этому значению точку кривой (). Для этого сначала разделим каждый отрезок ломаной P0 - P5 в отношении τ/(1-τ) (). Далее, получившиеся пять точек (P01τ, P12τ, и т.д., см. рис.) соединим отрезками, и каждый из них в свою очередь разделим в отношении τ/(1-τ) (). Будем повторять эту процедуру до тех пор, пока у нас не останется один отрезок P04τ - P15τ ). Точка, которая делит этот отрезок в отношении τ/(1-τ), и будет искомой точкой Pτ. Если изменять значения τ от 0 до 1 и для каждого находить точку таким образом, то мы получим кривую Безье n-го порядка, где n на 1 меньше числа точек (в данном случае порядок кривой будет пятый).
Можно доказать, что в аналитическом виде формула кривой Безье n-го порядка выглядит так:
Эта кривая представляет собой как бы сглаженный вариант ломаной P0...Pn. Можно выделить несколько полезных свойств кривых Безье. Так, эта кривая всегда проходит через точки P0 и Pn. Касательные к кривой в этих точках совпадают с направлениями отрезков [P0, P1] и [Pn-1, Pn] соответственно. Касательная в произвольной точке Pτ совпадает с направлением отрезка [P0(n-1)τ, P1nτ]. Часть кривой между точками P0 и Pτ так же представляет собой кривую Безье n-ного порядка, построенную на точках [P0, P01τ...P0(n-1)τ, Pτ]. Аналогично, ост авшаяся часть кривой - между точками Pτ и Pn - тоже является кривой Безье n-ного порядка, построенной на точках [Pτ, P1nτ...P(n-1)nτ, Pn].
На практике кривые Безье применяют для упрощения построения произвольных кривых, даже не заданных аналитически. Чаще всего для этого используют кривые Безье третьего порядка, так называемые кубические сплайны. Они достаточно просты для вычислений, но с их помощью уже можно с хорошей точностью приблизить любую гладкую кривую. Благодаря этому кривые Безье третьего порядка получили широкое распространение в компьютерной графике: их можно встретить практически в любом редакторе, позволяющем работать с векторными изображениями. В том числе в редакторе Flash MX.
Как нетрудно догадаться, для построения кривой Безье третьего порядка нужны четыре точки. Две точки задают начало и конец кривой (назовем их опорными), а две другие - направления касательных в опорных точках, их будем называть контрольными.
Уравнение кривой Безье третьего порядка выглядит так:
где P - точка на кривой, P0 и P3 - опорные точки, P1 и P2 - контрольные точки, τ - параметр, пробегающий значения от 0 до 1. Это уравнение можно также переписать в виде
В редакторах векторной графики пользователь имеет возможность передвигать опорные и контрольные точки, а также добавлять на кривую новые опорные точки
При добавлении новой точки кривая разбивается на две, но в первый момент это никак не влияет на ее форму. Однако получившиеся кривые можно редактировать независимо, и после редактирования они уже не могут быть преобразованы обратно в одну кривую Безье. Тем не менее, кривая в этом случае может остаться непрерывной и даже гладкой - если редактировать контрольные точки так, что на стыке двух участков опорная точка и две контрольные оставались на одной прямой, - она и будет касательной к кривой в данной точке (гладкость C1).
Как уже отмечалось, кубическими сплайнами можно аппроксимировать кривую любой сложности. Однако, напомним, что оператор curveTo рисует кривую Безье второго порядка. Эта кривая гораздо проще, чем кубический сплайн, и для аппроксимации произвольных линий таких кривых понадобилось бы больше.
Так, например, для построения окружности (или эллипса) с хорошей точностью достаточно четырех кубических сплайнов, а квадратичных надо как минимум 10-12
На эллипс - светло-серая кривая - практически невозможно отличить от кубического сплайна (тонкая черная кривая), построенного по точкам A, Aζ, Bξ и B. Контрольные точки Aζ и Bξ подбирались так, чтобы в точках A и B совпадали радиусы кривизны сплайна и исходной кривой (эллипса). Радиус кривизны кривой вычисляется по формуле
где точкой обозначается дифференцирование по параметру. Для эллипса
Такими же будут радиусы кривизны в этих точках и для кубического сплайна, если взять


Квадратичный сплайн (кривая Безье второго порядка, построенная по точкам A, C и B), как видно на рисунке, в точках A и B касается исходной кривой, но в промежутке между этими точками существенно от нее отклоняется.
Чтобы аппроксимировать произвольную кривую квадратичными сплайнами (нарисовать ее с помощью операторов curveTo), можно свести задачу к уже решенной: научиться аппроксимировать кубические сплайны квадратичными. Для этого прежде всего попробуем ответить на вопрос: сколько квадратичных сплайнов нужно для того, чтобы приблизить один кубический? Однозначного ответа на него не существует. Это зависит прежде всего от относительного расположения опорных и контрольных точек, а также от необходимой точности. В некоторых случаях приемлемый результат дает всего один сплайн. Однако бывает так, что, независимо от желаемой точности, обойтись одним сплайном принципиально невозможно. Так, например, из кривых, представленных на , только первую (а) можно было бы аппроксимировать одним сплайном. Дело в том, что кривая Безье второго порядка не содержит перегибов, поворотов больше чем на 180 градусов и самопересечений, а это значит, что для кривых с нужно как минимум по два квадратичных сплайна.
Рассмотрим более подробно случай, когда кубический сплайн заменяется одним квадратичным (все остальные случаи сводятся к нему разбивкой исходного сплайна на несколько более мелких).
Пусть у нас есть кривая Безье второго порядка:
Pквад(τ) = А(1 - τ)2 + 2С(1 - τ)τ + Вτ2
и
Pкуб(τ) = А(1 - τ)3 + 3Аξ(1 - τ)2τ + 3Вζ(1 - τ)τ2 + Вτ3
Подберем точку C так, чтобы кривая Pквад наиболее точно приближала кривую Pкуб. Очевидно, что это будет достигнуто, если в опорных точках кривые будут касаться друг друга. Поскольку в опорных точках направления касательных к кривым совпадают с направлением на ближайшую контрольную точку (это общее свойство кривых Безье), то это значит, что отрезки AAξ и AC, а также BBζ и BC должны совпадать. Иными словами, точка C - это точка пересечения прямых AAξ и BBζ. И больше никак на выбор контрольной точки для квадратичного сплайна мы повлиять не можем. Теперь следует оценить точность приближения и, в случае необходимости, разбить кубический сплайн на два или более участка, и с каждым из них повторить вышеописанную процедуру.
Дальше возникает вопрос: а как оценить точность приближения? Это можно сделать разными способами.
Так как точки A, Aξ и С лежат на одной прямой, то можно записать, что
Аξ = А(1 - ξ) + Сξ,
где ξ - действительное число. Нетрудно видеть, что при ξ = 0 точка Aξ совпадает с А, а при ξ = 1 - с точкой С. Аналогично
Вζ = В(1 - ζ) + Сζ.
Надо заметить, что ξ и ζ могут быть, вообще говоря, любыми: и большими единицы, и отрицательными. Если и ξ, и ζ отрицательны, то кривая Pкуб выглядит так, как на если знаки ξ и ζ разные, это соответствует наличию точки перегиба, если же
1/ξ + 1/ζ < 1,
то кривая имеет самопересечение.
Если
ζ - ξ = 2/3,
то кривая Pкуб вырождается - сокращаются члены, содержащие τ3 - и тождественно совпадает с Pквад. И чем больше ξ и ζ отличаются от 2/3 в ту или другую сторону, тем больше расхождение между кривыми. Однако этот метод расплывчат и мало информативен. В качестве меры расхождения между кривыми вернее всего было бы брать максимальное расстояние между ними. Но оно сложно считается, и разница будет не очень большой, если вместо него взять максимальное расстояние между точками на двух кривых при одном и том же значении параметра (напоказаны точки Pкуб(τ) и Pквад(τ) для τ = 1/2). Можно также использовать площадь заштрихованного на участка (взятую по модулю), ее можно посчитать аналитически.
Если условия работы программы такие, что отрисовка происходит намного медленнее, чем расчеты, то можно подумать над тем, как для каждого кубического сплайна обойтись не более чем двумя квадратичными. Для этого нужно оптимально подобрать значение параметра, по которому сплайн разбивается на две части - выбрать меру расхождения (например, площадь "щели" между кривыми), рассмотреть ее зависимость от точки разбиения и минимизировать. (Но бывают особые случаи: например, если есть точка перегиба, то разбивать нужно именно в ней, потому что участок, содержащий эту точку, приблизить квадратичным сплайном нельзя).
Однако такие ситуации, когда есть ресурсы для сложных расчетов, но надо экономить на рисовании объектов, встречаются довольно редко. Поэтому на практике эта задача решается следующим образом: если точность приближения недостаточна, сплайн разбивается на две части в точке τ = и дальше с каждой частью процедура повторяется. В таком случае получившихся кривых второго порядка может быть больше, чем при оптимальном выборе точки, но расчеты существенно упрощаются.
Но если перед вами стоит задача изобразить при помощи Флэш МХ кривую, заданную аналитически, то нет смысла прибегать к кривым Безье третьего порядка. Можно сразу использовать приближение квадратичными сплайнами. Ведь если у нас есть формула, задающая кривую (явно или параметрически), это значит, что мы можем узнать направление касательной в каждой точке. А если мы знаем направления касательных в двух не очень удаленных друг от друга точках, то мы можем соединить эти точки кривой при помощи оператора curveTo, передавая ему в качестве аргумента координаты точки пересечения касательных. Как, например, это было сделано в примере со спиралью ). Конечно, и здесь надо аккуратно обходить подводные камни: точки перегиба, изломы, самопересечения, повороты и другие подобные вещи. И можно попытаться разработать универсальный алгоритм разбиения произвольной кривой на точки, включающий в себя проверку точности приближения, обработку особых случаев и многое другое Но, как показывает опыт, гораздо удобнее и оптимальнее (с точки зрения и трудозатрат, и ресурсоемкости программы при ее выполнении - быстродействия, памяти и пр.) подбирать свой алгоритм для каждой конкретной задачи.

Сплошные и градиентные заливки
Кроме линий, во Флэш МХ есть возможность рисовать и протяженные области, залитые одним цветом или градиентом. Для этого есть методы beginFill, beginGradientFill и endFill.
Рассмотрим сначала сплошную заливку. Она делается при помощи методов beginFill и endFill, между вызовами которых с помощью операторов lineTo и curveTo рисуется ограничивающий контур (см.)
(строки примера пронумерованы, но номера, естественно, не являются частью кода).
1. _root.createEmptyMovieClip("fill_mc", 1);
2.
3. fill_mc.lineStyle(2, 0x9900ff, 100);
4.
5. fill_mc.beginFill(0xccccff, 100);
6. fill_mc.moveTo(50, 50);
7. fill_mc.lineTo(75, 0);
8. fill_mc.lineTo(100, 50);
9. fill_mc.lineTo(75, 100);
10. fill_mc.lineTo(50, 50);
11. fill_mc.endFill();
12. //--------------
   
Первый аргумент beginFill - цвет заливки (в0xccccff, светло-фиолетовый), второй - прозрачность заливки (меняется от 0 до 100, 0 - полностью прозрачный, 100, как в- полностью непрозрачный).
Здесь следует обратить внимание на несколько вещей. Во-первых, линия, нарисованная между вызовами beginFill и endFill должна быть непрерывной. То есть, оператор moveTo может быть вызван только один раз, в начале. Если он встречается несколько раз, то закрашена будет только область, нарисованная между последним вызовом moveTo и вызовом endFill. Если конечная позиция контура не совпадает с начальной (если бы вне было строчки 10), то при вызове оператора endFill контур замыкается прямой линией на начальную позицию (последний вызов moveTo).
Это документированное поведение операторов beginFill и endFill. Однако при некоторых условиях результат их выполнения может оказаться непредсказуемым. (В частности, результат заливки может зависеть от предыстории - от того, что делалось в этом клипе до вызова beginFill. Например, если там не встречалось ни одного оператора moveTo, заливка может лишь отдаленно напоминать то, что вы ожидаете получить.) Чтобы заливка работала устойчиво, нужно поступать следующим образом. Во-первых, с помощью moveTo перенести текущую позицию в начало контура до того, как будет вызван beginFill, во-вторых, конечная позиция контура должна совпадать с начальной.
Между beginFill и endFill может быть вызван метод lineStyle - если нужно разные части контура рисовать разным стилем. Если нужна заливка без контура, можно вызвать lineStyle без аргументов.
FlashMX позволяет делать заливки не только сплошным цветом, но и с плавным изменением цвета и прозрачности (градиентом). Для этого существует метод beginGradientFill.
Все, что до сих пор было сказано про beginFill (кроме описания аргументов), справедливо и для beginGradientFill. На аргументах beginGradientFill остановимся подробнее.
Часть примера показана в виде скриншота, чтобы сохранить нумерацию строк.
1. _root.createEmptyMovieClip("grad_mc", 1);
2. grad_mc._x = 200;
3. grad_mc._y = 200;
4. colors = [0x990000, 0xff0000, 0xff00ff, 0x000000, 0xff00ff, 0x0000ff, 0x0000cc];
5. alphas = [100,   100,   100,         100,   100,   100,     100   ];
6. ratios = [0,    1,    0x7d,   0x7f,   0x81,   0xfe,   0xff  ];
7. gradType="linear";
8. matrix = {matrixType: "box", x: 0, y: 0, w:100, h:100, r:0};
9.
10. grad_mc.lineStyle(2, 0x00cc00, 100);
11.    
12. grad_mc.beginGradientFill(gradType, colors, alphas, ratios, matrix);
13. grad_mc.moveTo(-200, -200);
14. grad_mc.lineTo(200, -200);
15. grad_mc.lineTo(200, 200);
16. grad_mc.lineTo(-200, 200);
17. grad_mc.lineTo(-200, -200);
18. grad_mc.endFill();
19.
20. //---------------
   
Пример 10.11
В результате выполнения этого кода будет нарисован квадрат 400*400 (его контур обозначен в строчках 13-17), часть которого будет представлять собой плавный переход одного цвета в другой. Эти цвета и переход между ними определяются аргументами метода beginGradientFill (строчка 12).
Первый аргумент, gradType (определен в строке 7) - тип градиента, представляет собой строчку "linear" или "radial". В первом случае области одинакового цвета будут представлять собой параллельные линии, во втором - эллипсы с одним центром.
Второй аргумент, colors, представляет собой массив, члены которого - ключевые цвета градиента. Цвета, которые находятся между ними, рассчитываются так, чтобы переход между ключевыми был линейным.
Третий аргумент, alphas, - массив прозрачностей ключевых цветов из массива colors. Длины этих массивов должны совпадать.
Четвертый, ratios, - относительные положения ключевых цветов градиента. Длина этого массива должна совпадать с длиной массивов colors и alphas. Каждый элемент массива places представляет собой число от 0 до 0xff. Если ratios[i]==0, то цвет, соответствующий colors[i] будет располагаться слева в случае градиента типа "linear" и в центре в случае "radial". 0xff соответствует правому краю и границе эллипса соответственно. Иными словами, компонент цвета сj (R, G или B) зависит от относительного положения r следующим образом:
cj(r) = colors[i]j + (r - ratios[i]j)((colors[i + 1]j + colors[i]j)/(ratios[i + 1]j + ratios[i]j)), ratios[i] r < ratios[i + 1] (10.1)
(В формуле 10.1 под colors[i]j понимается компонент цвета colors[i], R, G или B, соответствующий cj). Аналогичное выражение можно получить и для прозрачности :
(r) = alphas[i] + (r - ratios[i])((alphas[i + 1] + alphas[i])/(ratios[i + 1] + ratios[i])), ratios[i] r < ratios[i + 1] (10.2)
Чтобы результат выполнения операции beginGradientFill был предсказуемым, значения в массиве ratios должны быть монотонны. Контролировать процесс удобнее, если ratios[0] равно 0, а последний элемент в этом массиве равен 0xff, однако это не обязательно: в противном случае градиент просто дополняется соответственно первым и последним элементами массивов colors и alphas. К сожалению, на три перечисленных массива есть одно недокументированное ограничение: их длина не может быть больше 8. Точнее, может, но учитываются все равно только первые 8 элементов (с номерами от нулевого по седьмой включительно). Напомним также про необходимость делать все три массива одинаковой длины.
Вratios специально подобраны так, чтобы было видно, где кончается градиент и начинается сплошной цвет (переход между 0 и 1, а так же между 0xfe и 0xff выглядит как резкая граница), а кроме того, в центре присутствует четкая черная полоска.
И, наконец, аргумент matrix. Он задает расположение градиента на экране относительно локальных координат клипа. Остановимся на нем подробнее.
matrix - матрица трансформации - представляет собой объект, который должен содержать либо поля {a, b, c, d, e, f, g, h, i}, либо {matrixType, w, h, x, y, r}, как в
Рассмотрим первый вариант. В этом случае поля объекта matrix рассматриваются как матрица 3×3
которая используется следующим образом.
Предположим, что зависимость компонентов цвета и прозрачности от координат выражается функциями cj = fj(x, y), = f(x, y). Так как cj и зависят только от относительного положения r, то достаточно рассмотреть только функцию
r = ƒ (x, y) (10.4)
   
В случае градиента типа "linear" функция 10.4 принимает вид
ƒ (x, y) = 0xFF • (x + 0.5) (10.5)
   
То есть, от y цвет не зависит, а от x зависит линейно, при х = -0.5 r = 0, при x = 0.5 r = 0xff, так что весь градиент занимает вертикальную полоску шириной 1.
Если градиент имеет тип "radial", то функция 10.4 выглядит так:
ƒ (x, y) = 2 • 0xFF √x2 + y2 (10.6)
   
В этом случае r=0 соответствует точке (0, 0), а r = 0xFF - окружности x2 + y2 = 0.25, то есть весть градиент занимает круг с центром (0, 0) и диаметром 1.
С практической точки зрения эти функции все еще бессмысленны - никому не нужен градиент, умещающийся в 1 пиксель.
Преобразуем функцию 10.4 с помощью матрицы 10.3. Эта матрица описывает такое преобразование ƒƒM, что обратное к нему ƒMƒ можно записать через коэффициенты матрицы следующим образом:
ƒ (x, y) = ƒM(ax + dy + g,bx + ey + h) (10.7)
   
(Здесь реальные экранные координаты - это x = ax + dy + g,y = bx + ey + h, а x и y - аргументы функции 10.5 или 10.6.)
Функция fnof;M в 10.7 уже задает градиент любого размера и расположения на экране.
Обратите внимание, что параметры c, ƒ и i в преобразовании 10.7 не используется. Предполагается, что их значения не меняются: c = 0, ƒ = 0 и i = 1. То есть, матрица 10.3 на самом деле выглядит так:
В References написано, что в аргументе в объекте matrix должны присутствовать все 9 параметров, в том числе c, ƒ и i. Однако их значения (и даже их наличие или отсутствие) на вид градиента никак не влияют. Возникает естественный вопрос: если значения этих параметров ни на что не влияют, зачем вообще о них говорить? В ответ можно привести следующее рассуждение. Предположим, что функция ƒ определена не в двумерном, а в трехмерном пространстве. Тогда преобразование 10.7 выглядело бы так:
ƒ'(x, y, z) = ƒ'M(ax + dy + gz, bx + ey + hz, cz + fy + iz) (10.9)
   
То есть, просто представляло бы собой умножение вектора аргументов (x, y, z) на матрицу 10.3. Формула 10.7 получается из 10.9, если положить z 1. Еще одна причина, чтобы говорить об аргументе matrix как о матрице 3×3, - это удобство выполнения последовательных преобразований. Так, для того, чтобы получить матрицу преобразования, которое получается последовательным выполнением двух преобразований с матрицами M1 и M2, нужно просто перемножить эти матрицы.
Из 10.10 нетрудно получить, что если матрицы M1 и M2 имеют вид 10.8, то матрица M тоже имеет вид 10.8:
Не забывайте: важно, в каком порядке выполняются преобразования; в данном случае сначала M2 потом M1 (это справедливо постольку, поскольку мы имеем дело с обратным преобразованием ƒM ƒ. Если бы преобразование было прямым, то запись 10.11 означала бы, что выполняется сначала M1 потом M2).
Связь между компонентами матрицы 10.3 и параметрами градиента на первый взгляд кажется неочевидной. Чтобы это прояснить, рассмотрим несколько примеров.
Пусть матрица М имеет вид
Тогда преобразование градиента будет ƒ (x, y) = ƒM(W • x, H • y), то есть
r(x, y) = ƒM(x, y) = 255 • (x/W) + 0.5в случае градиента "linear"
r(x, y) = ƒM(x, y) = 255 • 0.5 √(x/W)2 + (y/H)2в случае "radial".
Таким образом, градиент просто растягивается в ширину на W и в высоту на H пикселей.
Чтобы проверить, как это будет выглядеть на экране, можно воспользоваться следующим примером:
_root.createEmptyMovieClip("grad_mc", 1);
grad_mc._x = 200;
grad_mc._y = 200;
colors = [0xffffff, 0x000000, 0xffffff, 0x000000, 0xffffff, 0xcccccc, 0xffffff];
alphas = [100,   100,   100,     100,   100,   100,     100   ];
ratios = [0,    1,    0x7d,   0x7f,   0x81,   0xfe,   0xff  ];
//Задаем компоненты матрицы.
//Чтобы задать новую матрицу, достаточно будет изменить эти строчки.
a0 = 100;  b0 = 0;
d0 = 0;   e0 = 50;
g0 = 0;   h0 = 0;
//-------------
gradType="radial"; //Задаем тип градиента
// Создаем матрицу
matrix = {a: _root.a0, b: _root.b0,
  d: _root.d0, e: _root.e0,
  g: _root.g0, h: _root.h0}
grad_mc.clear();
grad_mc.lineStyle(2, 0x00cc00, 100);
//Рисуем заливку градиентом
grad_mc.beginGradientFill(gradType, colors, alphas, ratios, matrix);
grad_mc.moveTo(-200, -200);
grad_mc.lineTo( 200, -200);
grad_mc.lineTo( 200, 200);
grad_mc.lineTo(-200, 200);
grad_mc.lineTo(-200, -200);
grad_mc.endFill();
//------------
// Кроме градиента нарисуем еще вспомогательный четырехугольник,
// в который превращается квадрат [-0.5<x<0.5, -0.5<y<0.5]
// после преобразования,задаваемого матрицей matrix.
with (matrix){
   grad_mc.lineStyle(0, 0x808080, 100);
   grad_mc.moveTo(0.5*(-a - d) + g, 0.5*(-b - e) + h);      
   grad_mc.lineTo(0.5*(-a + d) + g, 0.5*(-b + e) + h);
   grad_mc.lineTo(0.5*( a + d) + g, 0.5*( b + e) + h);
   grad_mc.lineTo(0.5*( a - d) + g, 0.5*( b - e) + h);
   grad_mc.lineTo(0.5*(-a - d) + g, 0.5*(-b - e) + h);
}
   
Результат применения матрицы 10.12 (W = 100, H = 50) для линейного и радиального градиентов (код приведен в примере 10.12).
Для поворота градиента на угол по часовой стрелке служит матрица следующего вида:
Матрица последовательного выполнения масштабирования и поворота будет выглядеть так:
Результат применения матрицы 10.14 (W = 100, H = 50, = *0.1) для линейного и радиального градиентов.
Поворот и масштабирование оставляют на месте центр градиента. Переместить этот центр затем можно с помощью матрицы следующего вида:
Для выполнения масштабирования, поворота и смещения служит матрица
Результат применения матрицы 10.16 (W = 100, H = 50, = 0.1, x0 = 50, y0 = 20) для линейного и радиального градиентов.
Матрица вида 10.16 наиболее очевидно и естественно описывает любое возможное расположение градиента. Это градиент, умещающийся в прямоугольник высотой H и шириной W, повернутый по часовой стрелке на угол , с центром в точке (x0, y0). Никакого более сложного градиента на Flash МХ сделать невозможно.
Если приглядеться к матрице 10.16 повнимательнее, можно заметить, что там используется только 5 независимых параметров: W, H, x0, y0, и . Однако в объекте matrix, который является аргументом метода beginGradientFill, их может быть шесть. Действительно, преобразование 10.16 оставляет исходный квадрат [-0.5 < x < 0.5, -0.5 < y < 0.5] прямоугольником, а в общем же случае он становится параллелограммом. Тем не менее к противоречию с тем, что сказано выше, это не приводит. В этом легко убедиться, если для произвольной матрицы вида 10.8 подобрать матрицу вида 10.16 с параметрами
 = atan2 (-d, e);
W = (ae - bd)/(√e2 + d2);
H = √e2 + d2;
x0 = g;
y0 = h (10.17)
   
для линейного градиента, и с параметрами
 = 0.5 • atan2 (2(ab + ed), a2 - b2 + d2 - e2);
W = Δ/√S - L;
H = Δ/√S + L;
x0 = g;
y0 = h (10.18)
   
где
Δ = ae - bd;
S = 0.5 (a2 + b2 + d2 + e2);
L = √S2 - Δ2;
   
для радиального. Градиент, построенный по этой матрице, будет идентичен исходному. Проверить это можно, слегка изменив код из:
matrix = {a: _root.a0, b: _root.b0,
                d: _root.d0, e: _root.e0,
                g: _root.g0, h: _root.h0}
with (matrix){
   _root.delta = a*e - b*d;
   if (gradType == "radial"){
      S=0.5*(e*e + a*a + d*d + b*b);
      L=Math.sqrt(S*S-delta*delta);
      h1 = delta / Math.sqrt(L + S);
      w1 = delta / Math.sqrt(S - L);
      phi1 = 0.5*Math.atan2( 2*(e*d + a*b), d*d + a*a - e*e - b*b );
   } else {
      h1=Math.sqrt(e*e + d*d);
      w1=delta/h1;
      phi1=Math.atan2(-d, e);
   }
   }
matrix1 = {a: w1*Math.cos(phi1), b: w1*Math.sin(phi1),
          d:-h1*Math.sin(phi1), e: h1*Math.cos(phi1),
          g: _root.g0,     h: _root.h0}
grad_mc.beginGradientFill(gradType, colors, alphas, ratios, matrix1);
grad_mc.moveTo(-200, -200);
grad_mc.lineTo( 200, -200);
grad_mc.lineTo( 200, 200);
grad_mc.lineTo(-200, 200);
grad_mc.lineTo(-200, -200);
grad_mc.endFill();
 
with (matrix){
   grad_mc.lineStyle(0, 0x808080, 100);
   grad_mc.moveTo(0.5*(-a - d) + g, 0.5*(-b - e) + h);      
   grad_mc.lineTo(0.5*(-a + d) + g, 0.5*(-b + e) + h);
   grad_mc.lineTo(0.5*( a + d) + g, 0.5*( b + e) + h);
   grad_mc.lineTo(0.5*( a - d) + g, 0.5*( b - e) + h);
   grad_mc.lineTo(0.5*(-a - d) + g, 0.5*(-b - e) + h);
}
   

В этом примере градиент строится по матрице matrix1 вида 10.16, а ограничивающий прямоугольник - по исходной матрице matrix. Нетрудно убедиться, что градиент оказывается вписанным в четырехугольник при любых параметрах исходной матрицы
 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика