1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
---
title: Transformations
slug: Web/API/Canvas_API/Tutorial/Transformations
translation_of: Web/API/Canvas_API/Tutorial/Transformations
---
<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Using_images", "Web/API/Canvas_API/Tutorial/Compositing")}}</div>
<div class="summary"><span>Ранее в этом уроке мы узнали о</span> <span style="background-color: #e6ecf9;"> <a href="https://translate.googleusercontent.com/translate_c?act=url&depth=1&hl=ru&ie=UTF8&prev=_t&rurl=translate.google.com&sl=en&sp=nmt4&tl=ru&u=https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes&usg=ALkJrhiuWce927wE2920fN95Jlcrf1HyUg">сетке холста</a></span> <span>и <strong>координатном пространстве</strong> .</span> <span> До сих пор мы использовали только сетку по умолчанию и изменили размер всего холста для наших нужд.</span> При преобразованиях существуют более мощные способы изменения исходных координат в различные положение, поворот сетки и даже масштабирование.</div>
<h2 id="Saving_and_restoring_state"><span style="background-color: #e6ecf9;">Сохранение и восстановление состояния</span></h2>
<p><span>Прежде чем перейти к методам преобразования, давайте рассмотрим два других метода, которые необходимы, когда вы начинаете создавать все более сложные рисунки.</span></p>
<dl>
<dt>{{domxref("CanvasRenderingContext2D.save", "save()")}}</dt>
<dd><span>Сохраняет все состояние холста.</span></dd>
<dt>{{domxref("CanvasRenderingContext2D.restore", "restore()")}}</dt>
<dd><span>Восстанавливает последнее сохранённое состояние холста.</span></dd>
</dl>
<p>Состояние холста сохраняется в стеке. Каждый раз, когда вызывается метод <code>save()</code>, текущее состояние отрисовки записывается в стек. Состояние отрисовки содержит:</p>
<ul>
<li>Трансформации, которые были применены (например, <code>translate</code>, <code>rotate</code> and <code>scale</code> – см. ниже).</li>
<li>Текущее значение следующих атрибутов: {{domxref("CanvasRenderingContext2D.strokeStyle", "strokeStyle")}}, {{domxref("CanvasRenderingContext2D.fillStyle", "fillStyle")}}, {{domxref("CanvasRenderingContext2D.globalAlpha", "globalAlpha")}}, {{domxref("CanvasRenderingContext2D.lineWidth", "lineWidth")}}, {{domxref("CanvasRenderingContext2D.lineCap", "lineCap")}}, {{domxref("CanvasRenderingContext2D.lineJoin", "lineJoin")}}, {{domxref("CanvasRenderingContext2D.miterLimit", "miterLimit")}}, {{domxref("CanvasRenderingContext2D.lineDashOffset", "lineDashOffset")}}, {{domxref("CanvasRenderingContext2D.shadowOffsetX", "shadowOffsetX")}}, {{domxref("CanvasRenderingContext2D.shadowOffsetY", "shadowOffsetY")}}, {{domxref("CanvasRenderingContext2D.shadowBlur", "shadowBlur")}}, {{domxref("CanvasRenderingContext2D.shadowColor", "shadowColor")}}, {{domxref("CanvasRenderingContext2D.globalCompositeOperation", "globalCompositeOperation")}}, {{domxref("CanvasRenderingContext2D.font", "font")}}, {{domxref("CanvasRenderingContext2D.textAlign", "textAlign")}}, {{domxref("CanvasRenderingContext2D.textBaseline", "textBaseline")}}, {{domxref("CanvasRenderingContext2D.direction", "direction")}}, {{domxref("CanvasRenderingContext2D.imageSmoothingEnabled", "imageSmoothingEnabled")}}.</li>
<li>Текущее значение границ вырезанного холста (<a href="/en-US/docs/Web/API/Canvas_API/Tutorial/Compositing#Clipping_paths">clipping path</a>), которые будут рассматриваться в следующем разделе.</li>
</ul>
<p>Вы можете вызывать метод <code>save()</code> столько раз, сколько захотите. В то же время, при вызове метода <code>restore()</code> последнее сохранённое состояние будет считано из стека, и все сохранённые настройки будут восстановлены.</p>
<h3 id="A_save_and_restore_canvas_state_example">Пример <font face="consolas, Liberation Mono, courier, monospace">сохранения и восстановления</font> состояния холста</h3>
<p>Здесь показано, как функционирует сохранение в стек состояния отрисовки на примере последовательной отрисовки набора прямоугольников.</p>
<pre class="brush: js; highlight:[5,10,15,18]">function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.fillRect(0, 0, 150, 150); // рисуем прямоугольник с настройками по умолчанию
ctx.save(); // сохраняем состояние
ctx.fillStyle = '#09F'; // вносим изменения в настройки
ctx.fillRect(15, 15, 120, 120); // рисуем прямоугольник с новыми настройками
ctx.save(); // сохраняем состояние
ctx.fillStyle = '#FFF'; // вносим изменения в настройки
ctx.globalAlpha = 0.5;
ctx.fillRect(30, 30, 90, 90); // рисуем прямоугольник с новыми настройками
ctx.restore(); // возвращаемся к предыдущим настройкам
ctx.fillRect(45, 45, 60, 60); // рисуем прямоугольник с восстановленными настройками
ctx.restore(); // возвращаемся к начальным настройкам
ctx.fillRect(60, 60, 30, 30); // рисуем прямоугольник с изначальными настройками
}</pre>
<div class="hidden">
<pre class="brush: html"><canvas id="canvas" width="150" height="150"></canvas></pre>
<pre class="brush: js">draw();</pre>
</div>
<p>Сначала рисуется большой прямоугольник с настройками по умолчанию. Затем мы сохраняем состояние холста, после чего изменяем цвет заливки. Затем рисуем второй синий прямоугольник меньшего размера и опять сохраняем состояние. Снова изменяем какие-то настройки и рисуем третий полупрозрачный белый прямоугольник.</p>
<p>До сих пор наши действия ничем не отличались от тех, что мы делали в предыдущем разделе. Однако, как только мы сделали первый вызов <code>restore(),</code> последнее сохранённое состояние отрисовки было восстановлено из стека, вернув все сохранённые настройки. Если бы мы не сохранили предыдущее состояние, используя <code>save()</code>, нам бы пришлось менять цвет заливки и настройки прозрачности вручную для возврата к предыдущему состоянию. Для каких-нибудь двух простых свойств это, может быть, сделать не так сложно. Но если таких свойств гораздо больше, это чревато очень быстрым разрастанием кода.</p>
<p>Когда второй вызов <code>restore()</code> сделан, изначальное состояние (то самое, которое было сделано перед первым вызовом <code>save</code>) восстанавливается и последний нарисованный прямоугольник вновь становится чёрным.</p>
<p>{{EmbedLiveSample("A_save_and_restore_canvas_state_example", "180", "180", "https://mdn.mozillademos.org/files/249/Canvas_savestate.png")}}</p>
<h2 id="Translating">Трансляция (смещение)</h2>
<p><img alt="" class="internal" src="https://mdn.mozillademos.org/files/234/Canvas_grid_translate.png" style="float: right;">Первый метод для трансформирования холста <code>translate()</code>. Он используется для перемещения холста в любую точку нашей сетки.</p>
<dl>
<dt>{{domxref("CanvasRenderingContext2D.translate", "translate(x, y)")}}</dt>
<dd>Перемещение холста на сетке. <code>x</code> и <code>y</code> - смещение по горизонтали и вертикали соответственно.</dd>
</dl>
<p>Неплохая идея - сохранять <code>canvas state</code> перед использованием любых трансформаций. Обычно удобнее использовать метод <code>restore</code>, чем выполнять обратные преобразования, чтобы вернуться к начальному состоянию. <span class="tlid-translation translation" lang="ru"><span title="">Кроме того, если вы выполняете преобразования внутри цикла не используя </span></span><code>save</code><span class="tlid-translation translation" lang="ru"><span title=""> и </span></span><code>restore</code><span class="tlid-translation translation" lang="ru"><span title="">, вы рискуете потерять часть рисунка, потому что он был нарисован за пределами края холста.</span></span></p>
<h3 id="A_translate_example">Пример использования <code>translate</code></h3>
<p><span class="tlid-translation translation" lang="ru"><span title="">Этот пример демонстрирует некоторые преимущества</span></span> при использовании смещения холста. Без использования метода <code>translate()</code> все прямоугольники будут отрисованы в одинаковой позиции (0,0). Метод <code>translate()</code> даёт возможность размещения прямоугольника в любой позиции без изменения параметров функции <code>fillRect()</code>. Это может дать некоторое упрощение для понимания и использования.</p>
<p>Внутри функции <code>draw()</code> мы вызываем <code>fillRect()</code> девять раз, используя два цикла <code>for</code>. Каждый раз мы сохраняем состояние холста, смещаем его, рисуем прямоугольник, а затем восстанавливаем исходное состояние. Заметьте, что <code>fillRect()</code> всегда использует одни и те же параметры, а изменение позиции фигуры осуществляется с помощью <code>translate()</code>.</p>
<pre class="brush: js; highlight:[7]">function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
ctx.save();
ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
ctx.translate(10 + j * 50, 10 + i * 50);
ctx.fillRect(0, 0, 25, 25);
ctx.restore();
}
}
}
</pre>
<div class="hidden">
<pre class="brush: html"><canvas id="canvas" width="150" height="150"></canvas></pre>
<pre class="brush: js">draw();</pre>
</div>
<p>{{EmbedLiveSample("A_translate_example", "160", "160", "https://mdn.mozillademos.org/files/9857/translate.png")}}</p>
<h2 id="Rotating">Поворот</h2>
<p><img alt="" class="internal" src="https://mdn.mozillademos.org/files/233/Canvas_grid_rotate.png" style="float: right;">Второй метод трансформации <code>rotate()</code>. Он используется для поворота нашего холста.</p>
<dl>
<dt>{{domxref("CanvasRenderingContext2D.rotate", "rotate(angle)")}}</dt>
<dd>Поворачивает наш холст по часовой стрелке вокруг начальной точки на угол <code>anglе</code> в радианах.</dd>
</dl>
<p>Центр поворота - всегда начало координат. Для изменения координат центра мы должны сместить холст, используя метод <code>translate()</code>.</p>
<h3 id="A_rotate_example">Пример использования<code>rotate</code></h3>
<p>В этом примере мы сначала используем <code>rotate()</code> для поворота прямоугольника относительно начала координат, а затем, используя <code>translate()</code> совместно с <code>rotate()</code> поворачиваем прямоугольник относительно его центра.</p>
<div class="note">
<p><strong>Памятка</strong>: Углы измеряются в радианах, а не в градусах. Для преобразования единиц используйте следующую формулу: <code>radians = (Math.PI/180)*degrees</code>.</p>
</div>
<pre class="brush: js; highlight:[9, 23]">function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
// left rectangles, rotate from canvas origin
ctx.save();
// blue rect
ctx.fillStyle = '#0095DD';
ctx.fillRect(30, 30, 100, 100);
ctx.rotate((Math.PI / 180) * 25);
// grey rect
ctx.fillStyle = '#4D4E53';
ctx.fillRect(30, 30, 100, 100);
ctx.restore();
// right rectangles, rotate from rectangle center
// draw blue rect
ctx.fillStyle = '#0095DD';
ctx.fillRect(150, 30, 100, 100);
ctx.translate(200, 80); // translate to rectangle center
// x = x + 0.5 * width
// y = y + 0.5 * height
ctx.rotate((Math.PI / 180) * 25); // rotate
ctx.translate(-200, -80); // translate back
// draw grey rect
ctx.fillStyle = '#4D4E53';
ctx.fillRect(150, 30, 100, 100);
}
</pre>
<p>Для поворота прямоугольника относительно его центра мы сначала смещаем начало координат, выполняем поворот, а затем выполняем обратное смещение к точке 0,0, и наконец рисуем прямоугольник.</p>
<div class="hidden">
<pre class="brush: html"><canvas id="canvas" width="300" height="200"></canvas></pre>
<pre class="brush: js">draw();</pre>
</div>
<p>{{EmbedLiveSample("A_rotate_example", "310", "210", "https://mdn.mozillademos.org/files/9859/rotate.png")}}</p>
<h2 id="Scaling">Масштабирование</h2>
<p>Следующий метод трансформации холста - scaling. Он используется для растяжения, сжатия и отражения координатной сетки. Он может использоваться для отрисовки растянутых или сжатых по осям фигур и изображений.</p>
<dl>
<dt>{{domxref("CanvasRenderingContext2D.scale", "scale(x, y)")}}</dt>
<dd>Масштабирует координатную сетку холста по горизонтали и вертикали. Оба параметра - вещественные числа. Значения меньше 1.0 уменьшают, а больше 1.0 увеличивают масштаб сетки. Значение 1.0 не изменяет его.</dd>
</dl>
<p>Используя отрицательные значения вы можете зеркально отразить направление осей. Например, используя <code>translate(0,canvas.height); scale(1,-1);</code> вы получите хорошо известную декартову систему координат с началом в нижнем левом углу.</p>
<p>По умолчанию единица координатной сетки точно соответствует одному пикселю. Если же вы, например, зададите масштабный коэффициент 0.5, то единица сетки будет равна половине пикселя, и нарисованная фигура будет иметь размер в два раза меньше оригинала. Наоборот, если задать масштабный коэффициент 2.0, единица сетки будет соответствовать двум пикселям, а нарисованная фигура станет в два раза больше.</p>
<h3 id="A_scale_example">Пример использования <code>scale</code></h3>
<p>В этом примере мы нарисуем прямоугольники, используя разные масштабные коэффициенты.</p>
<pre class="brush: js; highlight:[6,11]">function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
// рисуем масштабированный прямоугольник.
ctx.save();
ctx.scale(10, 3);
ctx.fillRect(1, 10, 10, 10);
ctx.restore();
// размещаем текст, отражённый по горизонтали
ctx.scale(-1, 1);
ctx.font = '48px serif';
ctx.fillText('MDN', -135, 120);
}
</pre>
<div class="hidden">
<pre class="brush: html"><canvas id="canvas" width="150" height="150"></canvas></pre>
<pre class="brush: js">draw();</pre>
</div>
<p>{{EmbedLiveSample("A_scale_example", "160", "160", "https://mdn.mozillademos.org/files/9861/scale.png")}}</p>
<h2 id="Transforms">Матричное преобразование</h2>
<p>В заключении рассмотрим метод, который вызывает изменения в соответствии с матрицей преобразования.</p>
<dl>
<dt>{{domxref("CanvasRenderingContext2D.transform", "transform(a, b, c, d, e, f)")}}</dt>
<dd>Накладывает матрицу преобразования, заданную параметрами, на текущую матрицу. Матрица преобразования задаётся следующим образом: <math><semantics><mrow><mo>[</mo><mtable columnalign="center center center" rowspacing="0.5ex"><mtr><mtd><mi>a</mi></mtd><mtd><mi>c</mi></mtd><mtd><mi>e</mi></mtd></mtr><mtr><mtd><mi>b</mi></mtd><mtd><mi>d</mi></mtd><mtd><mi>f</mi></mtd></mtr><mtr><mtd><mn>0</mn></mtd><mtd><mn>0</mn></mtd><mtd><mn>1</mn></mtd></mtr></mtable><mo>]</mo></mrow><annotation encoding="TeX">\left[ \begin{array}{ccc} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{array} \right]</annotation></semantics></math></dd>
</dl>
<dl>
<dd>If any of the arguments are <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity" title="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity">Infinity</a></code> the transformation matrix must be marked as infinite instead of the method throwing an exception.</dd>
</dl>
<p>Параметры функции:</p>
<dl>
<dt><code>a (m11)</code></dt>
<dd>Horizontal scaling.</dd>
<dt><em><code>b (m12)</code></em></dt>
<dd>Horizontal skewing.</dd>
<dt><code>c (m21)</code></dt>
<dd>Vertical skewing.</dd>
<dt><code>d (m22)</code></dt>
<dd>Vertical scaling.</dd>
<dt><code>e (dx)</code></dt>
<dd>Horizontal moving.</dd>
<dt><code>f (dy)</code></dt>
<dd>Vertical moving.</dd>
<dt>{{domxref("CanvasRenderingContext2D.setTransform", "setTransform(a, b, c, d, e, f)")}}</dt>
<dd>Сбрасывает текущую матрицу преобразования, а затем вызывает<code>transform()</code> в соответствии с аргументами.</dd>
<dt>{{domxref("CanvasRenderingContext2D.resetTransform", "resetTransform()")}}</dt>
<dd>Сбрасывает текущую матрицу преобразования к значению по умолчанию. Аналогично вызову <code>ctx.setTransform(1, 0, 0, 1, 0, 0);</code></dd>
</dl>
<h3 id="Пример_использования_transform_и_setTransform">Пример использования <code>transform</code> и <code>setTransform</code></h3>
<pre class="brush: js; highlight:[12,15]">function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var sin = Math.sin(Math.PI / 6);
var cos = Math.cos(Math.PI / 6);
ctx.translate(100, 100);
var c = 0;
for (var i = 0; i <= 12; i++) {
c = Math.floor(255 / 12 * i);
ctx.fillStyle = 'rgb(' + c + ', ' + c + ', ' + c + ')';
ctx.fillRect(0, 0, 100, 10);
ctx.transform(cos, sin, -sin, cos, 0, 0);
}
ctx.setTransform(-1, 0, 0, 1, 100, 100);
ctx.fillStyle = 'rgba(255, 128, 255, 0.5)';
ctx.fillRect(0, 50, 100, 100);
}
</pre>
<div class="hidden">
<pre class="brush: html"><canvas id="canvas" width="200" height="250"></canvas></pre>
<pre class="brush: js">draw();</pre>
</div>
<p>{{EmbedLiveSample("Пример_использования_transform_и_setTransform", "230", "280", "https://mdn.mozillademos.org/files/255/Canvas_transform.png")}}</p>
<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Using_images", "Web/API/Canvas_API/Tutorial/Compositing")}}</p>
|