OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 // | |
5 // Canvas API tests. Some of these are adapted from: | |
6 // | |
7 // http://www.html5canvastutorials.com/ | |
8 | |
9 library openglui_canvas_tests; | |
10 | |
11 import 'gl.dart'; | |
12 import 'dart:math' as Math; | |
13 | |
14 var ctx; | |
15 var width, height; | |
16 bool isDirty = true; | |
17 var canvas; | |
18 | |
19 void resize(int w, int h) { | |
20 width = w; | |
21 height = h; | |
22 } | |
23 | |
24 void setup(canvasp, int w, int h, int f) { | |
25 if (canvasp == null) { | |
26 log("Allocating canvas"); | |
27 canvas = new CanvasElement(width: w, height: h); | |
28 document.body.nodes.add(canvas); | |
29 } else { | |
30 log("Using parent canvas"); | |
31 canvas = canvasp; | |
32 } | |
33 canvas.onMouseDown.listen((e) { | |
34 ++testnum; | |
35 isDirty = true; | |
36 }); | |
37 canvas.onKeyDown.listen((e) { | |
38 ++testnum; | |
39 isDirty = true; | |
40 }); | |
41 ctx = canvas.getContext("2d"); | |
42 if (ctx == null) { | |
43 throw "Failed to get 2D context"; | |
44 } | |
45 resize(w, h); | |
46 window.requestAnimationFrame(update); | |
47 log("Done setup"); | |
48 } | |
49 | |
50 resetCanvas() { | |
51 ctx.globalCompositeOperation = "source-over"; | |
52 ctx.setTransform(1, 0, 0, 1, 0, 0); | |
53 ctx.fillStyle = "#FFFFFF"; | |
54 ctx.clearRect(0, 0, width, height); | |
55 ctx.shadowOffsetX = ctx.shadowOffsetY = ctx.shadowBlur = 0.0; | |
56 ctx.beginPath(); | |
57 } | |
58 | |
59 initTest(title) { | |
60 resetCanvas(); | |
61 ctx.font = "15px Courier"; | |
62 ctx.textAlign = 'left'; | |
63 ctx.fillStyle = "black"; | |
64 ctx.fillText(title, 20, 20); | |
65 } | |
66 | |
67 helloWorld() { | |
68 initTest("Hello world"); | |
69 ctx.font = "30px Courier"; | |
70 ctx.strokeStyle = "#7F7F7F"; | |
71 ctx.fillStyle = "#7F7F7F"; | |
72 ctx.textAlign = "center"; | |
73 ctx.lineWidth = 2; | |
74 ctx.strokeText("Align Center", width / 2, height / 4); | |
75 ctx.textAlign = "left"; | |
76 ctx.fillText("Align Left", width / 2, height / 2); | |
77 ctx.textAlign = "right"; | |
78 ctx.fillText("Align Right", width / 2, 3 * height / 4); | |
79 } | |
80 | |
81 blocks() { | |
82 initTest("fillRect/strokeRect"); | |
83 ctx.fillStyle = "#FF0000"; | |
84 ctx.fillRect(width / 10, height / 10, width / 2, height / 25); | |
85 ctx.fillStyle = "#00FF00"; | |
86 ctx.fillRect(width / 4, height / 5, width / 20, height / 8); | |
87 //ctx.fillStyle = "rgba(0,0,255,0.8)"; | |
88 ctx.strokeStyle = "rgba(128,128,128, 0.5)"; | |
89 //ctx.fillStyle = "#7F7F7F"; | |
90 ctx.strokeRect(width / 5, height / 10, width / 2, height / 8); | |
91 } | |
92 | |
93 square(left, top, width, height) { | |
94 ctx.beginPath(); | |
95 ctx.moveTo(left, top); | |
96 ctx.lineTo(left + width, top); | |
97 ctx.lineTo(left + width, top + height); | |
98 ctx.lineTo(left, top + height); | |
99 ctx.closePath(); //lineTo(left, top); | |
100 } | |
101 | |
102 squares() { | |
103 initTest("fill/stroke paths"); | |
104 ctx.strokeStyle = "black"; | |
105 ctx.fillStyle = "#FF0000"; | |
106 ctx.lineWidth = 4; | |
107 square(width / 10, height / 10, width / 2, height / 25); | |
108 ctx.fill(); | |
109 ctx.stroke(); | |
110 ctx.fillStyle = "#00FF00"; | |
111 square(width / 4, height / 5, width / 20, height / 8); | |
112 ctx.fill(); | |
113 ctx.stroke(); | |
114 ctx.fillStyle = "rgba(128,128,128, 0.5)"; | |
115 square(width / 5, height / 10, width / 2, height / 8); | |
116 ctx.fill(); | |
117 ctx.stroke(); | |
118 } | |
119 | |
120 lineJoin() { | |
121 initTest("Line joins"); | |
122 ctx.strokeStyle = "black"; | |
123 ctx.fillStyle = "#FF0000"; | |
124 ctx.lineWidth = 8; | |
125 ctx.lineJoin = "round"; | |
126 square(width / 10, height / 10, width / 2, height / 25); | |
127 ctx.stroke(); | |
128 ctx.fillStyle = "#00FF00"; | |
129 ctx.lineJoin = "bevel"; | |
130 square(width / 4, height / 5, width / 20, height / 8); | |
131 ctx.stroke(); | |
132 ctx.fillStyle = "rgba(128,128,128, 0.5)"; | |
133 ctx.lineJoin = "miter"; | |
134 square(width / 5, height / 10, width / 2, height / 8); | |
135 ctx.stroke(); | |
136 } | |
137 | |
138 grid() { | |
139 initTest("line strokes"); | |
140 ctx.lineWidth = 1; | |
141 for (var x = 0.5; x < width; x += 10) { | |
142 ctx.moveTo(x, 0); | |
143 ctx.lineTo(x, height); | |
144 } | |
145 for (var y = 0.5; y < height; y += 10) { | |
146 ctx.moveTo(0, y); | |
147 ctx.lineTo(width, y); | |
148 } | |
149 ctx.strokeStyle = "#eee"; | |
150 ctx.stroke(); | |
151 } | |
152 | |
153 line(x1, y1, x2, y2) { | |
154 ctx.beginPath(); | |
155 ctx.moveTo(x1, y1); | |
156 ctx.lineTo(x2, y2); | |
157 } | |
158 | |
159 strokeLines() { | |
160 initTest("line caps"); | |
161 ctx.lineWidth = height / 40; | |
162 ctx.strokeStyle = '#0000ff'; | |
163 // butt line cap (top line) | |
164 line(width / 4, height / 4, 3 * width / 4, height / 4); | |
165 ctx.lineCap = 'butt'; | |
166 ctx.stroke(); | |
167 | |
168 // round line cap (middle line) | |
169 line(width / 4, height / 2, 3 * width / 4, height / 2); | |
170 ctx.lineCap = 'round'; | |
171 ctx.stroke(); | |
172 | |
173 // square line cap (bottom line) | |
174 line(width / 4, 3 * height / 4, 3 * width / 4, 3 * height / 4); | |
175 ctx.lineCap = 'square'; | |
176 ctx.stroke(); | |
177 } | |
178 | |
179 colors() { | |
180 initTest("Colors"); | |
181 var colors = [ | |
182 "maroon", | |
183 "red", | |
184 "orange", | |
185 "yellow", | |
186 "olive", | |
187 "purple", | |
188 "fuschia", | |
189 "white", | |
190 "lime", | |
191 "green", | |
192 "navy", | |
193 "blue", | |
194 "aqua", | |
195 "teal", | |
196 "silver", | |
197 "gray", | |
198 "black" | |
199 ]; | |
200 | |
201 var i = 1; | |
202 var yinc = height / (2 * colors.length + 1); | |
203 | |
204 ctx.textAlign = "center"; | |
205 ctx.font = "${yinc}px Courier"; | |
206 | |
207 for (var color in colors) { | |
208 ctx.fillStyle = color; | |
209 ctx.fillRect(width / 4, i * 2 * yinc, width / 2, 3 * yinc / 2); | |
210 ctx.fillStyle = (color == "gray") ? "white" : "gray"; | |
211 ctx.fillText(color, width / 2, i * 2 * yinc + 7 * yinc / 8); | |
212 ++i; | |
213 } | |
214 } | |
215 | |
216 smiley() { | |
217 initTest("arcs"); | |
218 ctx.translate(width / 2 - 80, height / 2 - 75); | |
219 | |
220 ctx.beginPath(); | |
221 ctx.arc(100,80,75,0,Math.PI*2,true); | |
222 ctx.fillStyle = "rgb(255,255,204)"; | |
223 ctx.fill(); | |
224 ctx.stroke(); | |
225 | |
226 ctx.fillStyle = "black"; | |
227 ctx.beginPath(); | |
228 ctx.arc(80,55,8,0,Math.PI*2,true); | |
229 ctx.fill(); | |
230 | |
231 ctx.beginPath(); | |
232 ctx.arc(120,55,8,0,Math.PI*2,true); | |
233 ctx.fill(); | |
234 | |
235 ctx.beginPath(); | |
236 ctx.arc(100,85,10,4,Math.PI*2,true); | |
237 ctx.stroke(); | |
238 | |
239 ctx.beginPath(); | |
240 ctx.arc(100,95,30,0,Math.PI,false); | |
241 ctx.stroke(); | |
242 | |
243 ctx.setTransform(1, 0, 0, 1, 0, 0); | |
244 } | |
245 | |
246 rot(txt1, [txt2, sx = 1.0, sy = 1.0]) { | |
247 if (txt2 == null) txt2 = txt1; | |
248 ctx.font = "50px sans serif"; | |
249 ctx.translate(width / 2, height / 2); | |
250 ctx.textAlign = "right"; | |
251 ctx.fillStyle = "red"; | |
252 ctx.fillText(txt1, 0, 0); | |
253 ctx.rotate(Math.PI / 2); | |
254 ctx.scale(sx, sy); | |
255 ctx.fillStyle = "green"; | |
256 ctx.fillText(txt2, 0, 0); | |
257 ctx.scale(sx, sy); | |
258 ctx.rotate(Math.PI / 2); | |
259 ctx.fillStyle = "blue"; | |
260 ctx.fillText(txt1, 0, 0); | |
261 ctx.scale(sx, sy); | |
262 ctx.rotate(Math.PI / 2); | |
263 ctx.fillStyle = "yellow"; | |
264 ctx.fillText(txt2, 0, 0); | |
265 ctx.setTransform(1, 0, 0, 1, 0, 0); | |
266 } | |
267 | |
268 rotate() { | |
269 initTest("Rotation"); | |
270 rot("Dart"); | |
271 } | |
272 | |
273 alpha() { | |
274 initTest("Global alpha"); | |
275 ctx.fillStyle = "gray"; | |
276 ctx.fillRect(0, 0, width, height); | |
277 grid(); | |
278 ctx.globalAlpha = 0.5; | |
279 rot("Global", "Alpha"); | |
280 ctx.globalAlpha = 1.0; | |
281 } | |
282 | |
283 scale() { | |
284 initTest("Scale"); | |
285 rot("Scale", "Test", 0.8, 0.5); | |
286 } | |
287 | |
288 curves() { | |
289 initTest("Curves"); | |
290 ctx.beginPath(); | |
291 ctx.moveTo(188, 150); | |
292 ctx.quadraticCurveTo(288, 0, 388, 150); | |
293 ctx.lineWidth = 2; | |
294 ctx.strokeStyle = 'red'; | |
295 ctx.stroke(); | |
296 ctx.beginPath(); | |
297 ctx.moveTo(188, 130); | |
298 ctx.bezierCurveTo(140, 10, 388, 10, 388, 170); | |
299 ctx.lineWidth = 5; | |
300 ctx.strokeStyle = 'blue'; | |
301 ctx.stroke(); | |
302 var x1, x2, y1, y2, ex, ey; | |
303 ctx.beginPath(); | |
304 x1 = width / 4; | |
305 y1 = height / 2; | |
306 x2 = width / 2; | |
307 y2 = height / 4; | |
308 ex = 3 * width / 4; | |
309 ey = y1; | |
310 ctx.moveTo(x1, x2); | |
311 ctx.quadraticCurveTo(x2, y2, ex, ey); | |
312 ctx.lineWidth = 10; | |
313 ctx.strokeStyle = 'green'; | |
314 ctx.stroke(); | |
315 | |
316 ctx.beginPath(); | |
317 ctx.moveTo(188, 130); | |
318 x1 = width / 8; | |
319 x2 = 7 * width / 8; | |
320 y1 = height / 50; | |
321 y2 = y1; | |
322 ex = x2; | |
323 ey = height / 2; | |
324 ctx.bezierCurveTo(x1, y1, x2, y2, ex, ey); | |
325 ctx.lineWidth = 7; | |
326 ctx.strokeStyle = 'black'; | |
327 ctx.stroke(); | |
328 | |
329 // Draw a cloud | |
330 ctx.beginPath(); | |
331 var wscale = width / 578; | |
332 var hscale = height / 800; | |
333 ctx.translate(0, height / 2); | |
334 ctx.moveTo(170 * wscale, 80 * hscale); | |
335 ctx.bezierCurveTo(130 * wscale, 100 * hscale, | |
336 130 * wscale, 150 * hscale, | |
337 230 * wscale, 150 * hscale); | |
338 ctx.bezierCurveTo(250 * wscale, 180 * hscale, | |
339 320 * wscale, 180 * hscale, | |
340 340 * wscale, 150 * hscale); | |
341 ctx.bezierCurveTo(420 * wscale, 150 * hscale, | |
342 420 * wscale, 120 * hscale, | |
343 390 * wscale, 100 * hscale); | |
344 ctx.bezierCurveTo(430 * wscale, 40 * hscale, | |
345 370 * wscale, 30 * hscale, | |
346 340 * wscale, 50 * hscale); | |
347 ctx.bezierCurveTo(320 * wscale, 5 * hscale, | |
348 250 * wscale, 20 * hscale, | |
349 250 * wscale, 50 * hscale); | |
350 ctx.bezierCurveTo(200 * wscale, 5 * hscale, | |
351 150 * wscale, 20 * hscale, | |
352 170 * wscale, 80 * hscale); | |
353 ctx.closePath(); | |
354 ctx.lineWidth = 5; | |
355 ctx.fillStyle = 'gray'; | |
356 ctx.fill(); | |
357 } | |
358 | |
359 void shadows() { | |
360 initTest("Shadows"); | |
361 ctx.shadowBlur=20; | |
362 ctx.shadowColor="black"; | |
363 ctx.fillStyle="red"; | |
364 var w = width / 2; | |
365 if (w > height / 2) w = height / 2; | |
366 ctx.fillRect(width / 2 - w / 2, height / 4 - w / 2, w, w); | |
367 ctx.shadowOffsetX = 10; | |
368 ctx.shadowOffsetY = 10; | |
369 ctx.shadowColor="green"; | |
370 ctx.fillRect(width / 2 - w / 2, 3 * height / 4 - w / 2, w, w); | |
371 } | |
372 | |
373 void lineJoins() { | |
374 initTest("Line joins"); | |
375 ctx.lineWidth=10; | |
376 ctx.lineJoin="miter"; | |
377 ctx.moveTo(width / 2 - 25, height / 4 - 10); | |
378 ctx.lineTo(width /2 + 25, height / 4); | |
379 ctx.lineTo(width / 2 - 25, height / 4 + 10); | |
380 ctx.stroke(); | |
381 ctx.lineJoin="round"; | |
382 ctx.moveTo(width / 2 - 25, height / 2 - 10); | |
383 ctx.lineTo(width /2 + 25, height / 2); | |
384 ctx.lineTo(width / 2 - 25, height / 2 + 10); | |
385 ctx.stroke(); | |
386 ctx.lineJoin="bevel"; | |
387 ctx.moveTo(width / 2 - 25, 3 * height / 4 - 10); | |
388 ctx.lineTo(width /2 + 25, 3 * height / 4); | |
389 ctx.lineTo(width / 2 - 25, 3 * height / 4 + 10); | |
390 ctx.stroke(); | |
391 } | |
392 | |
393 void saveRestore() { | |
394 initTest("Save/restore state"); | |
395 ctx.font = "30px courier"; | |
396 ctx.fillStyle = "red"; | |
397 ctx.strokeStyle = "black"; | |
398 ctx.shadowBlur = 5; | |
399 ctx.shadowColor = "green"; | |
400 ctx.lineWidth = 1; | |
401 ctx.textAlign = "left"; | |
402 ctx.rotate(Math.PI / 30); | |
403 ctx.fillText("State 1", width /2, height / 6); | |
404 ctx.strokeText("State 1", width /2, height / 6); | |
405 ctx.save(); | |
406 | |
407 ctx.font = "40px sans serif"; | |
408 ctx.fillStyle = "blue"; | |
409 ctx.strokeStyle = "orange"; | |
410 ctx.shadowBlur = 8; | |
411 ctx.shadowOffsetX = 5; | |
412 ctx.shadowColor = "black"; | |
413 ctx.lineWidth = 2; | |
414 ctx.textAlign = "right"; | |
415 ctx.rotate(Math.PI / 30); | |
416 ctx.fillText("State 2", width /2, 2 * height / 6); | |
417 ctx.strokeText("State 2", width /2, 2 * height / 6); | |
418 ctx.save(); | |
419 | |
420 ctx.font = "50px times roman"; | |
421 ctx.fillStyle = "yellow"; | |
422 ctx.strokeStyle = "gray"; | |
423 ctx.shadowBlur = 8; | |
424 ctx.shadowOffsetX = 5; | |
425 ctx.shadowColor = "red"; | |
426 ctx.lineWidth = 3; | |
427 ctx.textAlign = "center"; | |
428 ctx.rotate(-Math.PI / 15); | |
429 ctx.fillText("State 3", width /2, 3 * height / 6); | |
430 ctx.strokeText("State 3", width /2, 3 * height / 6); | |
431 | |
432 ctx.restore(); | |
433 ctx.fillText("State 2", width /2, 4 * height / 6); | |
434 ctx.strokeText("State 2", width /2, 4 * height / 6); | |
435 | |
436 ctx.restore(); | |
437 ctx.fillText("State 1", width /2, 5 * height / 6); | |
438 ctx.strokeText("State 1", width /2, 5 * height / 6); | |
439 } | |
440 | |
441 void mirror() { | |
442 initTest("Mirror"); | |
443 // translate context to center of canvas | |
444 ctx.translate(width / 2, height / 2); | |
445 | |
446 // flip context horizontally | |
447 ctx.scale(-1, 1); | |
448 | |
449 ctx.font = '30pt Calibri'; | |
450 ctx.textAlign = 'center'; | |
451 ctx.fillStyle = 'blue'; | |
452 ctx.fillText('Magic Mirror', 0, 0); | |
453 } | |
454 | |
455 void oval() { | |
456 initTest("Path - pop state - stroke"); | |
457 var centerX = 0; | |
458 var centerY = 0; | |
459 var radius = 50; | |
460 | |
461 // save state | |
462 ctx.save(); | |
463 | |
464 // translate context | |
465 ctx.translate(width / 2, height / 2); | |
466 | |
467 // scale context horizontally | |
468 ctx.scale(2, 1); | |
469 | |
470 // draw circle which will be stretched into an oval | |
471 ctx.beginPath(); | |
472 ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); | |
473 | |
474 // restore to original state | |
475 ctx.restore(); | |
476 | |
477 // apply styling | |
478 ctx.fillStyle = '#8ED6FF'; | |
479 ctx.fill(); | |
480 ctx.lineWidth = 5; | |
481 ctx.strokeStyle = 'black'; | |
482 ctx.stroke(); | |
483 } | |
484 | |
485 void lineDash() { | |
486 initTest("Line dash"); | |
487 ctx.setLineDash([ 5, 8, 3 ]); | |
488 ctx.strokeStyle = "#FF0000"; | |
489 ctx.strokeRect(width / 10, height / 10, width / 2, height / 25); | |
490 ctx.lineDashOffset = 1; | |
491 ctx.strokeStyle = "#00FF00"; | |
492 ctx.strokeRect(width / 4, height / 5, width / 20, height / 8); | |
493 ctx.setLineDash([]); | |
494 ctx.strokeStyle = "#0000FF"; | |
495 ctx.strokeStyle = "rgba(128,128,128, 0.5)"; | |
496 ctx.strokeRect(width / 5, height / 10, width / 2, height / 8); | |
497 log("Width = $width"); | |
498 } | |
499 | |
500 void loadImage() { | |
501 initTest("Image loading"); | |
502 var imageObj = new ImageElement(); | |
503 // Setting src before onLoad is a more interesting test. | |
504 imageObj.src = 'chrome.png'; | |
505 imageObj.onLoad.listen((e) { | |
506 ctx.drawImage(e.target, 0, 0, width, height, 0, 0, width, height); | |
507 }); | |
508 } | |
509 | |
510 void clip() { | |
511 initTest("Clipping"); | |
512 var x = width / 2; | |
513 var y = height / 2; | |
514 var radius = height / 4; | |
515 var offset = 2 * radius / 3; | |
516 | |
517 ctx.save(); | |
518 ctx.beginPath(); | |
519 ctx.arc(x, y, radius, 0, 2 * Math.PI, false); | |
520 ctx.clip(); | |
521 | |
522 // draw blue circle inside clipping region | |
523 ctx.beginPath(); | |
524 ctx.arc(x - offset, y - offset, radius, 0, 2 * Math.PI, false); | |
525 ctx.fillStyle = 'blue'; | |
526 ctx.fill(); | |
527 | |
528 // draw yellow circle inside clipping region | |
529 ctx.beginPath(); | |
530 ctx.arc(x + offset, y, radius, 0, 2 * Math.PI, false); | |
531 ctx.fillStyle = 'yellow'; | |
532 ctx.fill(); | |
533 | |
534 // draw red circle inside clipping region | |
535 ctx.beginPath(); | |
536 ctx.arc(x, y + offset, radius, 0, 2 * Math.PI, false); | |
537 ctx.fillStyle = 'red'; | |
538 ctx.fill(); | |
539 | |
540 // Restore the canvas context to its original state | |
541 // before we defined the clipping region | |
542 ctx.restore(); | |
543 ctx.beginPath(); | |
544 ctx.arc(x, y, radius, 0, 2 * Math.PI, false); | |
545 ctx.lineWidth = 10; | |
546 ctx.strokeStyle = 'blue'; | |
547 ctx.stroke(); | |
548 } | |
549 | |
550 void shear() { | |
551 initTest("Transform"); | |
552 var rectWidth = width / 4; | |
553 var rectHeight = height / 8; | |
554 | |
555 // shear matrix: | |
556 // 1 sx 0 | |
557 // sy 1 0 | |
558 // 0 0 1 | |
559 | |
560 var sx = 0.75; | |
561 // .75 horizontal shear | |
562 var sy = 0; | |
563 // no vertical shear | |
564 | |
565 // translate context to center of canvas | |
566 ctx.translate(width / 2, height / 2); | |
567 | |
568 // apply custom transform | |
569 ctx.transform(1, sy, sx, 1, 0, 0); | |
570 | |
571 ctx.fillStyle = 'blue'; | |
572 ctx.fillRect(-rectWidth / 2, rectHeight / -2, rectWidth, rectHeight); | |
573 } | |
574 | |
575 void composite() { | |
576 initTest("Composition"); | |
577 var num = 0; | |
578 var numPerRow = width ~/ 150; | |
579 var tempCanvas = new CanvasElement(width: width, height:height); | |
580 var tempContext = tempCanvas.getContext("2d"); | |
581 log("Width = $width, numPerRow = $numPerRow\n"); | |
582 for (var mode in [ 'source-atop', 'source-in', | |
583 'source-out', 'source-over', | |
584 'destination-atop', 'destination-in', | |
585 'destination-out', 'destination-over', | |
586 'lighter', 'darker', | |
587 'xor', 'copy']) { | |
588 tempContext.save(); | |
589 tempContext.clearRect(0, 0, width, height); | |
590 tempContext.beginPath(); | |
591 tempContext.rect(0, 0, 55, 55); | |
592 tempContext.fillStyle = 'blue'; | |
593 tempContext.fill(); | |
594 | |
595 tempContext.globalCompositeOperation = mode; | |
596 tempContext.beginPath(); | |
597 tempContext.arc(50, 50, 35, 0, 2 * Math.PI, false); | |
598 tempContext.fillStyle = 'red'; | |
599 tempContext.fill(); | |
600 tempContext.restore(); | |
601 tempContext.font = '10pt Verdana'; | |
602 tempContext.fillStyle = 'black'; | |
603 tempContext.fillText(mode, 0, 100); | |
604 if (num > 0) { | |
605 if ((num % numPerRow) == 0) { | |
606 ctx.translate(-150 * (numPerRow-1), 150); | |
607 } else { | |
608 ctx.translate(150, 0); | |
609 } | |
610 } | |
611 ctx.drawImage(tempCanvas, 0, 0); | |
612 ++num; | |
613 } | |
614 } | |
615 | |
616 class Rectangle { | |
617 num x, y, width, height, borderWidth; | |
618 } | |
619 | |
620 var startTime = 0; | |
621 var myRectangle = null; | |
622 | |
623 void anim() { | |
624 if (myRectangle == null) { | |
625 myRectangle = new Rectangle(); | |
626 myRectangle.x = 250; | |
627 myRectangle.y = 70; | |
628 myRectangle.width = 100; | |
629 myRectangle.height = 50; | |
630 myRectangle.borderWidth = 5; | |
631 startTime = (new DateTime.now()).millisecondsSinceEpoch; | |
632 } | |
633 | |
634 var now = (new DateTime.now()).millisecondsSinceEpoch; | |
635 var time = now - startTime; | |
636 var amplitude = 150; | |
637 | |
638 // in ms | |
639 var period = 2000; | |
640 var centerX = width / 2 - myRectangle.width / 2; | |
641 var nextX = amplitude * Math.sin(time * 2 * Math.PI / period) + centerX; | |
642 myRectangle.x = nextX; | |
643 | |
644 // clear | |
645 ctx.clearRect(0, 0, width, height); | |
646 | |
647 // draw | |
648 | |
649 ctx.beginPath(); | |
650 ctx.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height); | |
651 ctx.fillStyle = '#8ED6FF'; | |
652 ctx.fill(); | |
653 ctx.lineWidth = myRectangle.borderWidth; | |
654 ctx.strokeStyle = 'black'; | |
655 ctx.stroke(); | |
656 } | |
657 | |
658 void linearGradient() { | |
659 initTest("Linear Gradient"); | |
660 ctx.rect(0, 0, width, height); | |
661 var grd = ctx.createLinearGradient(0, 0, width, height); | |
662 // light blue | |
663 grd.addColorStop(0, '#8ED6FF'); | |
664 // dark blue | |
665 grd.addColorStop(1, '#004CB3'); | |
666 ctx.fillStyle = grd; | |
667 ctx.fill(); | |
668 } | |
669 | |
670 void radialGradient() { | |
671 initTest("Radial Gradient"); | |
672 ctx.rect(0, 0, width, height); | |
673 var grd = ctx.createRadialGradient(238, 50, 10, 238, 50, 300); | |
674 // light blue | |
675 grd.addColorStop(0, '#8ED6FF'); | |
676 // dark blue | |
677 grd.addColorStop(1, '#004CB3'); | |
678 ctx.fillStyle = grd; | |
679 ctx.fill(); | |
680 } | |
681 | |
682 int testnum = 0; // Set this to -1 to start with last test. | |
683 | |
684 double x, y, z; | |
685 | |
686 onAccelerometer(double xx, double yy, double zz) { | |
687 x = xx; | |
688 y = yy; | |
689 z = zz; | |
690 } | |
691 | |
692 void update(num when) { | |
693 window.requestAnimationFrame(update); | |
694 if (testnum == 0) { | |
695 anim(); | |
696 return; | |
697 } | |
698 if (!isDirty) return; | |
699 switch(testnum) { | |
700 case 1: | |
701 helloWorld(); | |
702 break; | |
703 case 2: | |
704 blocks(); | |
705 break; | |
706 case 3: | |
707 squares(); | |
708 break; | |
709 case 4: | |
710 grid(); | |
711 break; | |
712 case 5: | |
713 strokeLines(); | |
714 break; | |
715 case 6: | |
716 lineJoin(); | |
717 break; | |
718 case 7: | |
719 colors(); | |
720 break; | |
721 case 8: | |
722 rotate(); | |
723 break; | |
724 case 9: | |
725 alpha(); | |
726 break; | |
727 case 10: | |
728 scale(); | |
729 break; | |
730 case 11: | |
731 curves(); | |
732 break; | |
733 case 12: | |
734 shadows(); | |
735 break; | |
736 case 13: | |
737 lineJoins(); | |
738 break; | |
739 case 14: | |
740 saveRestore(); | |
741 break; | |
742 case 15: | |
743 mirror(); | |
744 break; | |
745 case 16: | |
746 oval(); | |
747 break; | |
748 case 17: | |
749 lineDash(); | |
750 break; | |
751 case 18: | |
752 loadImage(); | |
753 break; | |
754 case 19: | |
755 clip(); | |
756 break; | |
757 case 20: | |
758 shear(); | |
759 break; | |
760 case 21: | |
761 composite(); | |
762 break; | |
763 case 22: | |
764 smiley(); | |
765 break; | |
766 case 23: | |
767 linearGradient(); | |
768 break; | |
769 case 24: | |
770 radialGradient(); | |
771 break; | |
772 case 25: | |
773 break; // Skip for now; this is really slow. | |
774 var rayTracer = new RayTracer(); | |
775 rayTracer.render(defaultScene(), ctx, width, height); | |
776 break; | |
777 default: | |
778 if (testnum < 0) { | |
779 testnum = 24; | |
780 } else { | |
781 testnum = 0; | |
782 } | |
783 return; | |
784 } | |
785 | |
786 isDirty = false; | |
787 } | |
788 | |
789 // Raytracer adapted from https://gist.github.com/mythz/3817303. | |
790 | |
791 Scene defaultScene() => | |
792 new Scene( | |
793 [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard), | |
794 new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny), | |
795 new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)], | |
796 [new Light(new Vector(-2.0, 2.5, 0.0), new Color(0.49, 0.07, 0.07) ), | |
797 new Light(new Vector(1.5, 2.5, 1.5), new Color(0.07, 0.07, 0.49) ), | |
798 new Light(new Vector(1.5, 2.5, -1.5), new Color(0.07, 0.49, 0.071) ), | |
799 new Light(new Vector(0.0, 3.5, 0.0), new Color(0.21, 0.21, 0.35) )], | |
800 new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0)) | |
801 ); | |
802 | |
803 | |
804 class Vector { | |
805 num x, y, z; | |
806 Vector(this.x, this.y, this.z); | |
807 | |
808 operator -(Vector v) => new Vector(x - v.x, y - v.y, z - v.z); | |
809 operator +(Vector v) => new Vector(x + v.x, y + v.y, z + v.z); | |
810 static times(num k, Vector v) => new Vector(k * v.x, k * v.y, k * v.z); | |
811 static num dot(Vector v1, Vector v2) => | |
812 v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; | |
813 static num mag(Vector v) => Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); | |
814 static Vector norm(Vector v) { | |
815 var _mag = mag(v); | |
816 var div = _mag == 0 ? double.INFINITY : 1.0 / _mag; | |
817 return times(div, v); | |
818 } | |
819 static Vector cross(Vector v1, Vector v2) { | |
820 return new Vector(v1.y * v2.z - v1.z * v2.y, | |
821 v1.z * v2.x - v1.x * v2.z, | |
822 v1.x * v2.y - v1.y * v2.x); | |
823 } | |
824 } | |
825 | |
826 class Color { | |
827 num r, g, b; | |
828 static final white = new Color(1.0, 1.0, 1.0); | |
829 static final grey = new Color(0.5, 0.5, 0.5); | |
830 static final black = new Color(0.0, 0.0, 0.0); | |
831 static final background = Color.black; | |
832 static final defaultColor = Color.black; | |
833 | |
834 Color(this.r,this.g,this.b); | |
835 static scale(num k, Color v) => new Color(k * v.r, k * v.g, k * v.b); | |
836 operator +(Color v) => new Color(r + v.r, g + v.g, b + v.b); | |
837 operator *(Color v) => new Color(r * v.r, g * v.g, b * v.b); | |
838 static _intColor(num d) => ((d > 1 ? 1 : d) * 255).toInt(); | |
839 static String toDrawingRGB(Color c) => | |
840 "rgb(${_intColor(c.r)}, ${_intColor(c.g)}, ${_intColor(c.b)})"; | |
841 } | |
842 | |
843 class Camera { | |
844 Vector pos, forward, right, up; | |
845 Camera (this.pos, Vector lookAt) { | |
846 var down = new Vector(0.0, -1.0, 0.0); | |
847 forward = Vector.norm(lookAt - pos); | |
848 right = Vector.times(1.5, Vector.norm(Vector.cross(forward, down))); | |
849 up = Vector.times(1.5, Vector.norm(Vector.cross(forward, right))); | |
850 } | |
851 } | |
852 | |
853 class Ray { | |
854 Vector start, dir; | |
855 Ray([this.start, this.dir]); | |
856 } | |
857 | |
858 class Intersection { | |
859 Thing thing; | |
860 Ray ray; | |
861 num dist; | |
862 Intersection(this.thing, this.ray, this.dist); | |
863 } | |
864 | |
865 class Light { | |
866 Vector pos; | |
867 Color color; | |
868 Light(this.pos, this.color); | |
869 } | |
870 | |
871 abstract class Surface { | |
872 int roughness; | |
873 Color diffuse(Vector pos); | |
874 Color specular(Vector pos); | |
875 num reflect(Vector pos); | |
876 } | |
877 | |
878 abstract class Thing { | |
879 Intersection intersect(Ray ray); | |
880 Vector normal(Vector pos); | |
881 Surface surface; | |
882 } | |
883 | |
884 class Scene { | |
885 List<Thing> things; | |
886 List<Light> lights; | |
887 Camera camera; | |
888 Scene([this.things,this.lights,this.camera]); | |
889 } | |
890 | |
891 class Sphere implements Thing { | |
892 num radius2, radius; | |
893 Vector center; | |
894 Surface surface; | |
895 | |
896 Sphere (this.center, this.radius, this.surface) { | |
897 this.radius2 = radius * radius; | |
898 } | |
899 normal(Vector pos) => Vector.norm(pos - center); | |
900 intersect(Ray ray) { | |
901 var eo = this.center - ray.start; | |
902 var v = Vector.dot(eo, ray.dir); | |
903 var dist = 0; | |
904 if (v >= 0) { | |
905 var disc = this.radius2 - (Vector.dot(eo, eo) - v * v); | |
906 if (disc >= 0) { | |
907 dist = v - Math.sqrt(disc); | |
908 } | |
909 } | |
910 return dist == 0 ? null : new Intersection(this, ray, dist); | |
911 } | |
912 } | |
913 | |
914 class Plane implements Thing { | |
915 Vector norm; | |
916 num offset; | |
917 Surface surface; | |
918 Plane(this.norm, this.offset, this.surface); | |
919 Vector normal(Vector pos) => norm; | |
920 Intersection intersect(Ray ray) { | |
921 var denom = Vector.dot(norm, ray.dir); | |
922 if (denom > 0) { | |
923 return null; | |
924 } else { | |
925 var dist = (Vector.dot(norm, ray.start) + offset) / (-denom); | |
926 return new Intersection(this, ray, dist); | |
927 } | |
928 } | |
929 } | |
930 | |
931 class CustomSurface implements Surface { | |
932 Color diffuseColor, specularColor; | |
933 int roughness; | |
934 num reflectPos; | |
935 CustomSurface(this.diffuseColor, this.specularColor, | |
936 this.reflectPos, this.roughness); | |
937 diffuse(pos) => diffuseColor; | |
938 specular(pos) => specularColor; | |
939 reflect(pos) => reflectPos; | |
940 } | |
941 | |
942 class CheckerBoardSurface implements Surface { | |
943 int roughness; | |
944 CheckerBoardSurface([this.roughness=150]); | |
945 diffuse(pos) => (pos.z.floor() + pos.x.floor()) % 2 != 0 | |
946 ? Color.white | |
947 : Color.black; | |
948 specular(pos) => Color.white; | |
949 reflect(pos) => (pos.z.floor() + pos.x.floor()) % 2 != 0 ? 0.1 : 0.7; | |
950 } | |
951 | |
952 class Surfaces { | |
953 static final shiny = new CustomSurface(Color.white, Color.grey, 0.7, 250); | |
954 static final checkerboard = new CheckerBoardSurface(); | |
955 } | |
956 | |
957 class RayTracer { | |
958 num _maxDepth = 5; | |
959 | |
960 Intersection _intersections(Ray ray, Scene scene) { | |
961 var closest = double.INFINITY; | |
962 Intersection closestInter = null; | |
963 for (Thing thing in scene.things) { | |
964 var inter = thing.intersect(ray); | |
965 if (inter != null && inter.dist < closest) { | |
966 closestInter = inter; | |
967 closest = inter.dist; | |
968 } | |
969 } | |
970 return closestInter; | |
971 } | |
972 | |
973 _testRay(Ray ray, Scene scene) { | |
974 var isect = _intersections(ray, scene); | |
975 return isect != null ? isect.dist : null; | |
976 } | |
977 | |
978 _traceRay(Ray ray, Scene scene, num depth) { | |
979 var isect = _intersections(ray, scene); | |
980 return isect == null ? Color.background : _shade(isect, scene, depth); | |
981 } | |
982 | |
983 _shade(Intersection isect, Scene scene, num depth) { | |
984 var d = isect.ray.dir; | |
985 var pos = Vector.times(isect.dist, d) + isect.ray.start; | |
986 var normal = isect.thing.normal(pos); | |
987 var reflectDir = d - | |
988 Vector.times(2, Vector.times(Vector.dot(normal, d), normal)); | |
989 var naturalColor = Color.background + | |
990 _getNaturalColor(isect.thing, pos, normal, reflectDir, scene); | |
991 var reflectedColor = (depth >= _maxDepth) ? Color.grey : | |
992 _getReflectionColor(isect.thing, pos, normal, reflectDir, | |
993 scene, depth); | |
994 return naturalColor + reflectedColor; | |
995 } | |
996 | |
997 _getReflectionColor(Thing thing, Vector pos, Vector normal, Vector rd, | |
998 Scene scene, num depth) => | |
999 Color.scale(thing.surface.reflect(pos), | |
1000 _traceRay(new Ray(pos, rd), scene, depth + 1)); | |
1001 | |
1002 _getNaturalColor(Thing thing, Vector pos, Vector norm, Vector rd, | |
1003 Scene scene) { | |
1004 var addLight = (col, light) { | |
1005 var ldis = light.pos - pos; | |
1006 var livec = Vector.norm(ldis); | |
1007 var neatIsect = _testRay(new Ray(pos, livec), scene); | |
1008 var isInShadow = neatIsect == null ? false : | |
1009 (neatIsect <= Vector.mag(ldis)); | |
1010 if (isInShadow) { | |
1011 return col; | |
1012 } else { | |
1013 var illum = Vector.dot(livec, norm); | |
1014 var lcolor = (illum > 0) ? Color.scale(illum, light.color) | |
1015 : Color.defaultColor; | |
1016 var specular = Vector.dot(livec, Vector.norm(rd)); | |
1017 var scolor = (specular > 0) | |
1018 ? Color.scale(Math.pow(specular, thing.surface.roughness), | |
1019 light.color) | |
1020 : Color.defaultColor; | |
1021 return col + (thing.surface.diffuse(pos) * lcolor) | |
1022 + (thing.surface.specular(pos) * scolor); | |
1023 } | |
1024 }; | |
1025 return scene.lights.fold(Color.defaultColor, addLight); | |
1026 } | |
1027 | |
1028 render(Scene scene, CanvasRenderingContext2D ctx, num screenWidth, | |
1029 num screenHeight) { | |
1030 var getPoint = (x, y, camera) { | |
1031 var recenterX = (x) => (x - (screenWidth / 2.0)) / 2.0 / screenWidth; | |
1032 var recenterY = (y) => - (y - (screenHeight / 2.0)) / 2.0 / screenHeight; | |
1033 return Vector.norm(camera.forward | |
1034 + Vector.times(recenterX(x), camera.right) | |
1035 + Vector.times(recenterY(y), camera.up)); | |
1036 }; | |
1037 for (int y = 0; y < screenHeight; y++) { | |
1038 for (int x = 0; x < screenWidth; x++) { | |
1039 var color = _traceRay(new Ray(scene.camera.pos, | |
1040 getPoint(x, y, scene.camera) ), scene, 0); | |
1041 ctx.fillStyle = Color.toDrawingRGB(color); | |
1042 ctx.fillRect(x, y, x + 1, y + 1); | |
1043 } | |
1044 } | |
1045 } | |
1046 } | |
1047 | |
OLD | NEW |