| 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 |