├── .gitignore ├── README.md ├── canvasquery.js ├── package.json └── src ├── framework.js └── text ├── textBoundaries.js └── wrappedText.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-project 2 | *.sublime-workspace -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Canvas Query 2 | 3 | Chainable canvas API for gamedevelopers 4 | 5 | ```javascript 6 | var layer = cq() 7 | .fillStyle("#ff0000") 8 | .fillRect(0, 0, 32, 32); 9 | ``` 10 | 11 | # Documentation 12 | 13 | [Read online](http://canvasquery.com) 14 | 15 | # Framework 16 | 17 | If you are looking for a simple game framework that will handle mouse, keyboard and basic structure see [playgroundjs](http://playgroundjs.com) -------------------------------------------------------------------------------- /canvasquery.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Canvas Query r9 4 | 5 | http://canvasquery.com 6 | 7 | (c) 2012-2016 http://rezoner.net 8 | 9 | Canvas Query may be freely distributed under the MIT license. 10 | 11 | r9 12 | 13 | + even more precise fontHeight and fontTop 14 | + textBoundaries and wrappedText use same alg for maxWidth when newline is detected 15 | 16 | r8 17 | 18 | + improved matchPalette performance 19 | + defaultFont 20 | 21 | r7 22 | 23 | + more accurate fontHeight() 24 | + fillText respects no antialiasing when using pixel font 25 | + textBaseline("top") consistent among browsers 26 | + align state is added to the stack 27 | + new canvases are pulled from the pool 28 | + filter (experimetnal) 29 | 30 | r6 31 | 32 | + ImageBitmap support 33 | + drawImageCentered 34 | + drawRegionCentered 35 | + default textBaseline 36 | + resizeBounds 37 | 38 | r5 39 | 40 | ! fixed: leaking arguments in fastApply bailing out optimization 41 | + cacheText 42 | + compare 43 | + checkerboard 44 | 45 | */ 46 | 47 | 48 | (function() { 49 | 50 | var COCOONJS = false; 51 | 52 | var Canvas = window.HTMLCanvasElement; 53 | var orgImage = window.Image; 54 | var Image = window.HTMLImageElement; 55 | var ImageBitmap = window.ImageBitmap || window.HTMLImageElement; 56 | var COCOONJS = navigator.isCocoonJS; 57 | 58 | var cq = function(selector) { 59 | 60 | if (arguments.length === 0) { 61 | 62 | var canvas = cq.pool(); 63 | 64 | canvas.width = window.innerWidth; 65 | canvas.height = window.innerHeight; 66 | 67 | } else if (typeof selector === "string") { 68 | 69 | var canvas = document.querySelector(selector); 70 | 71 | } else if (typeof selector === "number") { 72 | 73 | var canvas = cq.pool(); 74 | 75 | canvas.width = arguments[0]; 76 | canvas.height = arguments[1]; 77 | 78 | } else if (selector instanceof Image) { 79 | 80 | var canvas = cq.pool(); 81 | 82 | canvas.width = selector.width; 83 | canvas.height = selector.height; 84 | canvas.getContext("2d").drawImage(selector, 0, 0); 85 | 86 | } else if (selector instanceof ImageBitmap) { 87 | 88 | var canvas = cq.pool(); 89 | 90 | canvas.width = selector.width; 91 | canvas.height = selector.height; 92 | canvas.getContext("2d").drawImage(selector, 0, 0); 93 | 94 | } else if (selector instanceof cq.Layer) { 95 | 96 | return selector; 97 | 98 | } else { 99 | 100 | var canvas = selector; 101 | 102 | } 103 | 104 | return new cq.Layer(canvas); 105 | 106 | }; 107 | 108 | cq.lineSpacing = 1.0; 109 | cq.defaultFont = ""; 110 | cq.textBaseline = "alphabetic"; 111 | cq.matchPalettePrecision = 10; 112 | 113 | cq.palettes = { 114 | 115 | db16: ["#140c1c", "#442434", "#30346d", "#4e4a4e", "#854c30", "#346524", "#d04648", "#757161", "#597dce", "#d27d2c", "#8595a1", "#6daa2c", "#d2aa99", "#6dc2ca", "#dad45e", "#deeed6"], 116 | db32: ["#000000", "#222034", "#45283c", "#663931", "#8f563b", "#df7126", "#d9a066", "#eec39a", "#fbf236", "#99e550", "#6abe30", "#37946e", "#4b692f", "#524b24", "#323c39", "#3f3f74", "#306082", "#5b6ee1", "#639bff", "#5fcde4", "#cbdbfc", "#ffffff", "#9badb7", "#847e87", "#696a6a", "#595652", "#76428a", "#ac3232", "#d95763", "#d77bba", "#8f974a", "#8a6f30"], 117 | c64: ["#000000", "#6a5400", "#68ae5c", "#8a8a8a", "#adadad", "#636363", "#c37b75", "#c9d684", "#ffffff", "#984b43", "#a3e599", "#79c1c8", "#9b6739", "#9b51a5", "#52429d", "#8a7bce"], 118 | gameboy: ["#0f380f", "#306230", "#8bac0f", "#9bbc0f"], 119 | sega: ["#000000", "#555500", "#005500", "#555555", "#55aa00", "#550000", "#aaffaa", "#aaaaaa", "#ff5555", "#005555", "#550055", "#aaaa55", "#ffffaa", "#aa5555", "#ffaa55", "#ffff55", "#ffffff", "#ffaaaa", "#000055", "#55aaaa", "#aa0000", "#ff5500", "#ffaa00", "#aa5500", "#ff0000", "#ffaaff", "#aa55aa", "#aaaa00", "#aaff00", "#aaaaff", "#5555aa", "#aaffff"], 120 | cga: ["#000000", "#ff5555", "#55ff55", "#ffff55"], 121 | nes: ["#7C7C7C", "#0000FC", "#0000BC", "#4428BC", "#940084", "#A80020", "#A81000", "#881400", "#503000", "#007800", "#006800", "#005800", "#004058", "#000000", "#000000", "#000000", "#BCBCBC", "#0078F8", "#0058F8", "#6844FC", "#D800CC", "#E40058", "#F83800", "#E45C10", "#AC7C00", "#00B800", "#00A800", "#00A844", "#008888", "#000000", "#000000", "#000000", "#F8F8F8", "#3CBCFC", "#6888FC", "#9878F8", "#F878F8", "#F85898", "#F87858", "#FCA044", "#F8B800", "#B8F818", "#58D854", "#58F898", "#00E8D8", "#787878", "#000000", "#000000", "#FCFCFC", "#A4E4FC", "#B8B8F8", "#D8B8F8", "#F8B8F8", "#F8A4C0", "#F0D0B0", "#FCE0A8", "#F8D878", "#D8F878", "#B8F8B8", "#B8F8D8", "#00FCFC", "#F8D8F8", "#000000"], 122 | 123 | }; 124 | 125 | /* 126 | 127 | cq.loadImages(); 128 | 129 | cq.run(function(){ 130 | 131 | }); 132 | 133 | */ 134 | 135 | cq.cocoon = function(selector) { 136 | if (arguments.length === 0) { 137 | var canvas = cq.createCocoonCanvas(window.innerWidth, window.innerHeight); 138 | window.addEventListener("resize", function() {}); 139 | } else if (typeof selector === "string") { 140 | var canvas = document.querySelector(selector); 141 | } else if (typeof selector === "number") { 142 | var canvas = cq.createCocoonCanvas(arguments[0], arguments[1]); 143 | } else if (selector instanceof Image) { 144 | var canvas = cq.createCocoonCanvas(selector); 145 | } else if (selector instanceof cq.Layer) { 146 | return selector; 147 | } else { 148 | var canvas = selector; 149 | } 150 | 151 | return new cq.Layer(canvas); 152 | } 153 | 154 | 155 | cq.extend = function() { 156 | for (var i = 1; i < arguments.length; i++) { 157 | for (var j in arguments[i]) { 158 | arguments[0][j] = arguments[i][j]; 159 | } 160 | } 161 | 162 | return arguments[0]; 163 | }; 164 | 165 | cq.augment = function() { 166 | for (var i = 1; i < arguments.length; i++) { 167 | _.extend(arguments[0], arguments[i]); 168 | arguments[i](arguments[0]); 169 | } 170 | }; 171 | 172 | cq.distance = function(x1, y1, x2, y2) { 173 | if (arguments.length > 2) { 174 | var dx = x1 - x2; 175 | var dy = y1 - y2; 176 | 177 | return Math.sqrt(dx * dx + dy * dy); 178 | } else { 179 | return Math.abs(x1 - y1); 180 | } 181 | }; 182 | 183 | /* fast.js */ 184 | 185 | cq.fastApply = function(subject, thisContext, args) { 186 | 187 | switch (args.length) { 188 | case 0: 189 | return subject.call(thisContext); 190 | case 1: 191 | return subject.call(thisContext, args[0]); 192 | case 2: 193 | return subject.call(thisContext, args[0], args[1]); 194 | case 3: 195 | return subject.call(thisContext, args[0], args[1], args[2]); 196 | case 4: 197 | return subject.call(thisContext, args[0], args[1], args[2], args[3]); 198 | case 5: 199 | return subject.call(thisContext, args[0], args[1], args[2], args[3], args[4]); 200 | case 6: 201 | return subject.call(thisContext, args[0], args[1], args[2], args[3], args[4], args[5]); 202 | case 7: 203 | return subject.call(thisContext, args[0], args[1], args[2], args[3], args[4], args[5], args[6]); 204 | case 8: 205 | return subject.call(thisContext, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); 206 | case 9: 207 | return subject.call(thisContext, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); 208 | default: 209 | return subject.apply(thisContext, args); 210 | } 211 | 212 | }; 213 | 214 | cq.extend(cq, { 215 | 216 | smoothing: true, 217 | 218 | blend: function(below, above, mode, mix) { 219 | 220 | if (typeof mix === "undefined") mix = 1; 221 | 222 | var below = cq(below); 223 | var mask = below.clone(); 224 | var above = cq(above); 225 | 226 | below.save(); 227 | below.globalAlpha(mix); 228 | below.globalCompositeOperation(mode); 229 | below.drawImage(above.canvas, 0, 0); 230 | below.restore(); 231 | 232 | mask.save(); 233 | mask.globalCompositeOperation("source-in"); 234 | mask.drawImage(below.canvas, 0, 0); 235 | mask.restore(); 236 | 237 | return mask; 238 | }, 239 | 240 | matchColor: function(color, palette) { 241 | var rgbPalette = []; 242 | 243 | for (var i = 0; i < palette.length; i++) { 244 | rgbPalette.push(cq.color(palette[i])); 245 | } 246 | 247 | var imgData = cq.color(color); 248 | 249 | var difList = []; 250 | for (var j = 0; j < rgbPalette.length; j++) { 251 | var rgbVal = rgbPalette[j]; 252 | var rDif = Math.abs(imgData[0] - rgbVal[0]), 253 | gDif = Math.abs(imgData[1] - rgbVal[1]), 254 | bDif = Math.abs(imgData[2] - rgbVal[2]); 255 | difList.push(rDif + gDif + bDif); 256 | } 257 | 258 | var closestMatch = 0; 259 | for (var j = 0; j < palette.length; j++) { 260 | if (difList[j] < difList[closestMatch]) { 261 | closestMatch = j; 262 | } 263 | } 264 | 265 | return palette[closestMatch]; 266 | }, 267 | 268 | temp: function(width, height) { 269 | 270 | if (!this.tempLayer) { 271 | 272 | this.tempLayer = cq(1, 1); 273 | 274 | } 275 | 276 | if (width instanceof Image || width instanceof ImageBitmap) { 277 | this.tempLayer.width = width.width; 278 | this.tempLayer.height = width.height; 279 | this.tempLayer.context.drawImage(width, 0, 0); 280 | } else if (width instanceof Canvas) { 281 | this.tempLayer.width = width.width; 282 | this.tempLayer.height = width.height; 283 | this.tempLayer.context.drawImage(width, 0, 0); 284 | } else if (width instanceof CanvasQuery.Layer) { 285 | this.tempLayer.width = width.width; 286 | this.tempLayer.height = width.height; 287 | this.tempLayer.context.drawImage(width.canvas, 0, 0); 288 | } else { 289 | this.tempLayer.width = width; 290 | this.tempLayer.height = height; 291 | } 292 | 293 | return this.tempLayer; 294 | }, 295 | 296 | wrapValue: function(value, min, max) { 297 | if (value < min) return max + (value % max); 298 | if (value >= max) return value % max; 299 | return value; 300 | }, 301 | 302 | limitValue: function(value, min, max) { 303 | return value < min ? min : value > max ? max : value; 304 | }, 305 | 306 | mix: function(a, b, amount) { 307 | return a + (b - a) * amount; 308 | }, 309 | 310 | hexToRgb: function(hex) { 311 | if (hex.length === 7) return ['0x' + hex[1] + hex[2] | 0, '0x' + hex[3] + hex[4] | 0, '0x' + hex[5] + hex[6] | 0]; 312 | else return ['0x' + hex[1] + hex[1] | 0, '0x' + hex[2] + hex[2] | 0, '0x' + hex[3] + hex[3] | 0]; 313 | }, 314 | 315 | rgbToHex: function(r, g, b) { 316 | return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1, 7); 317 | }, 318 | 319 | extractCanvas: function(o) { 320 | 321 | if (o.canvas) return o.canvas; 322 | else return o; 323 | 324 | }, 325 | 326 | compare: function(a, b) { 327 | 328 | a = this.extractCanvas(a); 329 | b = this.extractCanvas(b); 330 | 331 | a = a.getContext("2d").getImageData(0, 0, a.width, a.height).data; 332 | b = b.getContext("2d").getImageData(0, 0, b.width, b.height).data; 333 | 334 | if (a.length !== b.length) return false; 335 | 336 | for (var i = 0; i < a.length; i++) { 337 | 338 | if (a[i] !== b[i]) return false; 339 | 340 | } 341 | 342 | return true; 343 | 344 | }, 345 | 346 | /* author: http://mjijackson.com/ */ 347 | 348 | rgbToHsl: function(r, g, b) { 349 | 350 | if (r instanceof Array) { 351 | b = r[2]; 352 | g = r[1]; 353 | r = r[0]; 354 | } 355 | 356 | r /= 255, g /= 255, b /= 255; 357 | var max = Math.max(r, g, b), 358 | min = Math.min(r, g, b); 359 | var h, s, l = (max + min) / 2; 360 | 361 | if (max == min) { 362 | h = s = 0; // achromatic 363 | } else { 364 | var d = max - min; 365 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 366 | switch (max) { 367 | case r: 368 | h = (g - b) / d + (g < b ? 6 : 0); 369 | break; 370 | case g: 371 | h = (b - r) / d + 2; 372 | break; 373 | case b: 374 | h = (r - g) / d + 4; 375 | break; 376 | } 377 | h /= 6; 378 | } 379 | 380 | return [h, s, l]; 381 | }, 382 | 383 | /* author: http://mjijackson.com/ */ 384 | 385 | hue2rgb: function(p, q, t) { 386 | if (t < 0) t += 1; 387 | if (t > 1) t -= 1; 388 | if (t < 1 / 6) return p + (q - p) * 6 * t; 389 | if (t < 1 / 2) return q; 390 | if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; 391 | return p; 392 | }, 393 | 394 | hslToRgb: function(h, s, l) { 395 | var r, g, b; 396 | 397 | if (s == 0) { 398 | r = g = b = l; // achromatic 399 | } else { 400 | 401 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 402 | var p = 2 * l - q; 403 | r = this.hue2rgb(p, q, h + 1 / 3); 404 | g = this.hue2rgb(p, q, h); 405 | b = this.hue2rgb(p, q, h - 1 / 3); 406 | } 407 | 408 | return [r * 255 | 0, g * 255 | 0, b * 255 | 0]; 409 | }, 410 | 411 | rgbToHsv: function(r, g, b) { 412 | if (r instanceof Array) { 413 | b = r[2]; 414 | g = r[1]; 415 | r = r[0]; 416 | } 417 | 418 | r = r / 255, g = g / 255, b = b / 255; 419 | var max = Math.max(r, g, b), 420 | min = Math.min(r, g, b); 421 | var h, s, v = max; 422 | 423 | var d = max - min; 424 | s = max == 0 ? 0 : d / max; 425 | 426 | if (max == min) { 427 | h = 0; // achromatic 428 | } else { 429 | switch (max) { 430 | case r: 431 | h = (g - b) / d + (g < b ? 6 : 0); 432 | break; 433 | case g: 434 | h = (b - r) / d + 2; 435 | break; 436 | case b: 437 | h = (r - g) / d + 4; 438 | break; 439 | } 440 | h /= 6; 441 | } 442 | 443 | return [h, s, v]; 444 | }, 445 | 446 | hsvToRgb: function(h, s, v) { 447 | var r, g, b; 448 | 449 | var i = Math.floor(h * 6); 450 | var f = h * 6 - i; 451 | var p = v * (1 - s); 452 | var q = v * (1 - f * s); 453 | var t = v * (1 - (1 - f) * s); 454 | 455 | switch (i % 6) { 456 | case 0: 457 | r = v, g = t, b = p; 458 | break; 459 | case 1: 460 | r = q, g = v, b = p; 461 | break; 462 | case 2: 463 | r = p, g = v, b = t; 464 | break; 465 | case 3: 466 | r = p, g = q, b = v; 467 | break; 468 | case 4: 469 | r = t, g = p, b = v; 470 | break; 471 | case 5: 472 | r = v, g = p, b = q; 473 | break; 474 | } 475 | 476 | return [r * 255, g * 255, b * 255]; 477 | }, 478 | 479 | color: function() { 480 | var result = new cq.Color(); 481 | result.parse(arguments[0], arguments[1]); 482 | return result; 483 | }, 484 | 485 | poolArray: [], 486 | 487 | pool: function() { 488 | 489 | if (!this.poolArray.length) { 490 | for (var i = 0; i < 100; i++) { 491 | this.poolArray.push(this.createCanvas(1, 1)); 492 | } 493 | } 494 | 495 | return this.poolArray.pop(); 496 | 497 | }, 498 | 499 | reuse: function(object) { 500 | 501 | return this.recycle(object); 502 | 503 | }, 504 | 505 | recycle: function(object) { 506 | 507 | if (object instanceof CanvasQuery.Layer) { 508 | 509 | this.poolArray.push(object.canvas); 510 | 511 | } else { 512 | 513 | this.poolArray.push(object); 514 | 515 | } 516 | 517 | }, 518 | 519 | setContextSmoothing: function(context, smoothing) { 520 | 521 | context.mozImageSmoothingEnabled = smoothing; 522 | context.msImageSmoothingEnabled = smoothing; 523 | context.webkitImageSmoothingEnabled = smoothing; 524 | context.imageSmoothingEnabled = smoothing; 525 | 526 | }, 527 | 528 | createCanvas: function(width, height) { 529 | 530 | var result = document.createElement("canvas"); 531 | 532 | if (arguments[0] instanceof Image || arguments[0] instanceof Canvas || arguments[0] instanceof ImageBitmap) { 533 | 534 | var image = arguments[0]; 535 | 536 | result.width = image.width; 537 | result.height = image.height; 538 | 539 | result.getContext("2d").drawImage(image, 0, 0); 540 | 541 | } else { 542 | 543 | result.width = width; 544 | result.height = height; 545 | 546 | } 547 | 548 | return result; 549 | 550 | }, 551 | 552 | createCocoonCanvas: function(width, height) { 553 | 554 | var result = document.createElement("screencanvas"); 555 | 556 | if (arguments[0] instanceof Image) { 557 | var image = arguments[0]; 558 | result.width = image.width; 559 | result.height = image.height; 560 | result.getContext("2d").drawImage(image, 0, 0); 561 | } else { 562 | result.width = width; 563 | result.height = height; 564 | } 565 | 566 | return result; 567 | 568 | }, 569 | 570 | createImageData: function(width, height) { 571 | 572 | return cq.createCanvas(width, height).getContext("2d").createImageData(width, height); 573 | 574 | } 575 | 576 | }); 577 | 578 | cq.Layer = function(canvas) { 579 | 580 | this.context = canvas.getContext("2d"); 581 | this.canvas = canvas; 582 | this.prevAlignX = []; 583 | this.prevAlignY = []; 584 | this.alignX = 0; 585 | this.alignY = 0; 586 | this.aligned = false; 587 | this.update(); 588 | 589 | }; 590 | 591 | cq.Layer.prototype = { 592 | 593 | constructor: cq.Layer, 594 | 595 | update: function() { 596 | 597 | var smoothing = cq.smoothing; 598 | 599 | if (typeof this.smoothing !== "undefined") smoothing = this.smoothing; 600 | 601 | this.context.mozImageSmoothingEnabled = smoothing; 602 | this.context.msImageSmoothingEnabled = smoothing; 603 | this.context.webkitImageSmoothingEnabled = smoothing; 604 | this.context.imageSmoothingEnabled = smoothing; 605 | 606 | if (cq.defaultFont) this.context.font = cq.defaultFont; 607 | 608 | this.context.textBaseline = cq.textBaseline; 609 | 610 | if (COCOONJS) Cocoon.Utils.setAntialias(smoothing); 611 | }, 612 | 613 | appendTo: function(selector) { 614 | 615 | if (typeof selector === "object") { 616 | 617 | var element = selector; 618 | 619 | } else { 620 | 621 | var element = document.querySelector(selector); 622 | 623 | } 624 | 625 | element.appendChild(this.canvas); 626 | 627 | return this; 628 | }, 629 | 630 | a: function(a) { 631 | 632 | if (arguments.length) { 633 | 634 | this.previousAlpha = this.globalAlpha(); 635 | 636 | return this.globalAlpha(a); 637 | 638 | } else { 639 | 640 | return this.globalAlpha(); 641 | 642 | } 643 | 644 | }, 645 | 646 | ra: function() { 647 | 648 | return this.a(this.previousAlpha); 649 | 650 | }, 651 | /* 652 | drawImage: function() { 653 | 654 | if (!this.alignX && !this.alignY) { 655 | this.context.call 656 | } 657 | 658 | return this; 659 | 660 | 661 | }, 662 | 663 | restore: function() { 664 | this.context.restore(); 665 | this.alignX = 0; 666 | this.alignY = 0; 667 | }, 668 | */ 669 | 670 | realign: function() { 671 | 672 | this.alignX = this.prevAlignX[this.prevAlignX.length - 1]; 673 | this.alignY = this.prevAlignY[this.prevAlignY.length - 1]; 674 | 675 | return this; 676 | 677 | }, 678 | 679 | align: function(x, y) { 680 | 681 | if (typeof y === "undefined") y = x; 682 | 683 | this.alignX = x; 684 | this.alignY = y; 685 | 686 | return this; 687 | }, 688 | 689 | 690 | /* save translate align rotate scale */ 691 | 692 | stars: function(x, y, alignX, alignY, rotation, scaleX, scaleY) { 693 | 694 | if (typeof alignX === "undefined") alignX = 0.5; 695 | if (typeof alignY === "undefined") alignY = 0.5; 696 | if (typeof rotation === "undefined") rotation = 0; 697 | if (typeof scaleX === "undefined") scaleX = 1.0; 698 | if (typeof scaleY === "undefined") scaleY = scaleX; 699 | 700 | this.save(); 701 | this.translate(x, y); 702 | this.align(alignX, alignY); 703 | this.rotate(rotation); 704 | this.scale(scaleX, scaleY); 705 | 706 | return this; 707 | }, 708 | 709 | tars: function(x, y, alignX, alignY, rotation, scaleX, scaleY) { 710 | 711 | if (typeof alignX === "undefined") alignX = 0.5; 712 | if (typeof alignY === "undefined") alignY = 0.5; 713 | if (typeof rotation === "undefined") rotation = 0; 714 | if (typeof scaleX === "undefined") scaleX = 1.0; 715 | if (typeof scaleY === "undefined") scaleY = scaleX; 716 | 717 | this.translate(x, y); 718 | this.align(alignX, alignY); 719 | this.rotate(rotation); 720 | this.scale(scaleX, scaleY); 721 | 722 | return this; 723 | 724 | }, 725 | 726 | webkit: ('WebkitAppearance' in document.documentElement.style), 727 | 728 | fillText: function(text, x, y, gap) { 729 | 730 | text = String(text); 731 | 732 | if (!text.length) return; 733 | 734 | var webkitHack = !cq.smoothing && (this.fontHeight() <= 64) && ('WebkitAppearance' in document.documentElement.style); 735 | 736 | if (webkitHack) { 737 | 738 | var scale = this.webkit ? 4 : 5; 739 | 740 | var canvas = cq.pool(); 741 | var context = canvas.getContext("2d"); 742 | 743 | context.font = this.context.font; 744 | 745 | var realWidth = context.measureText(text).width; 746 | var width = Math.ceil(realWidth); 747 | var gap = gap || (width - realWidth); 748 | 749 | var height = this.fontHeight(); 750 | 751 | canvas.width = width * scale; 752 | canvas.height = height * scale; 753 | 754 | cq.setContextSmoothing(context, false); 755 | 756 | // context.fillStyle = "#fff"; 757 | // context.fillRect(0,0,canvas.width, canvas.height); 758 | 759 | context.font = this.context.font; 760 | context.fillStyle = this.context.fillStyle; 761 | context.textBaseline = "top"; 762 | 763 | context.scale(scale, scale); 764 | context.fillText(text, gap, -this.fontTop()); 765 | 766 | if (this.context.textAlign === "center") x -= width * 0.5; 767 | else if (this.context.textAlign === "right") x -= width; 768 | 769 | this.drawImage(canvas, 0, 0, canvas.width, canvas.height, x, y, canvas.width / scale, canvas.height / scale); 770 | 771 | } else { 772 | 773 | 774 | this.context.fillText(text, x, y - this.fontTop()); 775 | 776 | 777 | } 778 | 779 | return this; 780 | 781 | }, 782 | 783 | fillRect: function() { 784 | 785 | if (this.alignX || this.alignY) { 786 | 787 | this.context.fillRect(arguments[0] - arguments[2] * this.alignX | 0, arguments[1] - arguments[3] * this.alignY | 0, arguments[2], arguments[3]); 788 | 789 | } else { 790 | 791 | this.context.fillRect(arguments[0], arguments[1], arguments[2], arguments[3]); 792 | 793 | } 794 | 795 | // cq.fastApply(this.context.fillRect, this.context, arguments); 796 | 797 | return this; 798 | 799 | }, 800 | 801 | strokeRect: function() { 802 | 803 | if (this.alignX || this.alignY) { 804 | 805 | this.context.strokeRect(arguments[0] - arguments[2] * this.alignX | 0, arguments[1] - arguments[3] * this.alignY | 0, arguments[2], arguments[3]); 806 | 807 | } else { 808 | 809 | this.context.strokeRect(arguments[0], arguments[1], arguments[2], arguments[3]); 810 | 811 | } 812 | 813 | // cq.fastApply(this.context.strokeRect, this.context, arguments); 814 | 815 | return this; 816 | 817 | }, 818 | 819 | drawImage: function(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) { 820 | 821 | if (this.alignX || this.alignY) { 822 | 823 | if (sWidth == null) { 824 | sx -= image.width * this.alignX | 0; 825 | sy -= image.height * this.alignY | 0; 826 | } else { 827 | dx -= dWidth * this.alignX | 0; 828 | dy -= dHeight * this.alignY | 0; 829 | } 830 | 831 | } 832 | 833 | if (sWidth == null) { 834 | 835 | this.context.drawImage(image, sx, sy); 836 | 837 | } else if (dx == null) { 838 | 839 | this.context.drawImage(image, sx, sy, sWidth, sHeight); 840 | 841 | } else { 842 | 843 | this.context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); 844 | 845 | } 846 | 847 | // cq.fastApply(this.context.drawImage, this.context, arguments); 848 | 849 | return this; 850 | 851 | }, 852 | 853 | drawImageCentered: function(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) { 854 | 855 | if (sWidth == null) { 856 | sx -= image.width * 0.5 | 0; 857 | sy -= image.height * 0.5 | 0; 858 | } else { 859 | dx -= dWidth * 0.5 | 0; 860 | dy -= dHeight * 0.5 | 0; 861 | } 862 | 863 | if (sWidth == null) { 864 | 865 | this.context.drawImage(image, sx, sy); 866 | 867 | } else if (dx == null) { 868 | 869 | this.context.drawImage(image, sx, sy, sWidth, sHeight); 870 | 871 | } else { 872 | 873 | this.context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); 874 | 875 | } 876 | 877 | return this; 878 | 879 | }, 880 | 881 | save: function() { 882 | 883 | this.prevAlignX.push(this.alignX); 884 | this.prevAlignY.push(this.alignY); 885 | 886 | this.context.save(); 887 | 888 | return this; 889 | 890 | }, 891 | 892 | restore: function() { 893 | 894 | this.realign(); 895 | this.alignX = this.prevAlignX.pop(); 896 | this.alignY = this.prevAlignY.pop(); 897 | this.context.restore(); 898 | 899 | return this; 900 | 901 | }, 902 | 903 | drawTile: function(image, x, y, frameX, frameY, frameWidth, frameHeight, frames, frame) { 904 | 905 | }, 906 | 907 | checkerboard: function(x, y, w, h, grid, colorA, colorB) { 908 | 909 | var tx = w / grid | 0; 910 | var ty = h / grid | 0; 911 | 912 | this.save(); 913 | this.rect(x, y, w, h).clip(); 914 | 915 | for (var i = 0; i <= tx; i++) { 916 | for (var j = 0; j <= ty; j++) { 917 | 918 | 919 | if (j % 2) var color = i % 2 ? colorA : colorB; 920 | else var color = i % 2 ? colorB : colorA; 921 | 922 | this.fillStyle(color); 923 | this.fillRect(x + i * grid, y + j * grid, grid, grid); 924 | 925 | } 926 | } 927 | 928 | this.restore(); 929 | 930 | }, 931 | 932 | drawAtlasFrame: function(atlas, frame, x, y) { 933 | 934 | var frame = atlas.frames[frame]; 935 | 936 | this.drawRegion( 937 | atlas.image, 938 | frame.region, 939 | x - frame.width * this.alignX + frame.offset[0] + frame.region[2] * this.alignX, 940 | y - frame.height * this.alignY + frame.offset[1] + frame.region[3] * this.alignY 941 | ); 942 | 943 | return this; 944 | 945 | }, 946 | 947 | 948 | imageFill: function(image, width, height) { 949 | 950 | var scale = Math.max(width / image.width, height / image.height); 951 | 952 | this.save(); 953 | this.scale(scale, scale); 954 | this.drawImage(image, 0, 0); 955 | this.restore(); 956 | 957 | }, 958 | 959 | drawRegion: function(image, region, x, y, scale) { 960 | 961 | scale = scale || 1; 962 | 963 | return this.drawImage( 964 | image, region[0], region[1], region[2], region[3], 965 | x | 0, y | 0, region[2] * scale | 0, region[3] * scale | 0 966 | ); 967 | 968 | }, 969 | 970 | drawRegionCentered: function(image, region, x, y, scale) { 971 | 972 | scale = scale || 1; 973 | 974 | return this.drawImageCentered( 975 | image, region[0], region[1], region[2], region[3], 976 | x | 0, y | 0, region[2] * scale | 0, region[3] * scale | 0 977 | ); 978 | 979 | }, 980 | 981 | cache: function() { 982 | 983 | return this.clone().canvas; 984 | 985 | }, 986 | 987 | popup: function() { 988 | 989 | window.open(this.canvas.toDataURL()); 990 | 991 | return this; 992 | 993 | }, 994 | 995 | blendOn: function(what, mode, mix) { 996 | cq.blend(what, this, mode, mix); 997 | 998 | return this; 999 | }, 1000 | 1001 | posterize: function(pc, inc) { 1002 | pc = pc || 32; 1003 | inc = inc || 4; 1004 | var imgdata = this.getImageData(0, 0, this.width, this.height); 1005 | var data = imgdata.data; 1006 | 1007 | for (var i = 0; i < data.length; i += inc) { 1008 | data[i] -= data[i] % pc; // set value to nearest of 8 possibilities 1009 | data[i + 1] -= data[i + 1] % pc; // set value to nearest of 8 possibilities 1010 | data[i + 2] -= data[i + 2] % pc; // set value to nearest of 8 possibilities 1011 | } 1012 | 1013 | this.putImageData(imgdata, 0, 0); // put image data to canvas 1014 | 1015 | return this; 1016 | }, 1017 | 1018 | posterizeAlpha: function(pc, inc) { 1019 | pc = pc || 32; 1020 | inc = inc || 4; 1021 | var imgdata = this.getImageData(0, 0, this.width, this.height); 1022 | var data = imgdata.data; 1023 | 1024 | for (var i = 0; i < data.length; i += inc) { 1025 | 1026 | data[i + 3] -= data[i + 3] % pc; // set value to nearest of 8 possibilities 1027 | 1028 | } 1029 | 1030 | this.putImageData(imgdata, 0, 0); // put image data to canvas 1031 | 1032 | return this; 1033 | }, 1034 | 1035 | bw: function(pc) { 1036 | pc = 128; 1037 | var imgdata = this.getImageData(0, 0, this.width, this.height); 1038 | var data = imgdata.data; 1039 | // 8-bit: rrr ggg bb 1040 | for (var i = 0; i < data.length; i += 4) { 1041 | var v = ((data[i] + data[i + 1] + data[i + 2]) / 3); 1042 | 1043 | v = (v / 128 | 0) * 128; 1044 | //data[i] = v; // set value to nearest of 8 possibilities 1045 | //data[i + 1] = v; // set value to nearest of 8 possibilities 1046 | data[i + 2] = (v / 255) * data[i]; // set value to nearest of 8 possibilities 1047 | 1048 | } 1049 | 1050 | this.putImageData(imgdata, 0, 0); // put image data to canvas 1051 | }, 1052 | 1053 | blend: function(what, mode, mix) { 1054 | if (typeof what === "string") { 1055 | var color = what; 1056 | what = cq(this.canvas.width, this.canvas.height); 1057 | what.fillStyle(color).fillRect(0, 0, this.canvas.width, this.canvas.height); 1058 | } 1059 | 1060 | var result = cq.blend(this, what, mode, mix); 1061 | 1062 | this.canvas = result.canvas; 1063 | this.context = result.context; 1064 | 1065 | return this; 1066 | }, 1067 | 1068 | textWithBackground: function(text, x, y, background, padding) { 1069 | var w = this.measureText(text).width; 1070 | var h = this.fontHeight() * 0.8; 1071 | var f = this.fillStyle(); 1072 | var padding = padding || 2; 1073 | 1074 | var a = this.context.textAlign; 1075 | 1076 | this.fillStyle(background).fillRect(x - padding * 2, y - padding, w + padding * 4, h + padding * 2) 1077 | this.fillStyle(f).textAlign("left").textBaseline("top").fillText(text, x, y); 1078 | 1079 | return this; 1080 | }, 1081 | 1082 | fillCircle: function(x, y, r) { 1083 | this.context.beginPath(); 1084 | this.context.arc(x, y, r, 0, Math.PI * 2); 1085 | this.context.fill(); 1086 | return this; 1087 | }, 1088 | 1089 | strokeCircle: function(x, y, r) { 1090 | this.context.beginPath(); 1091 | this.context.arc(x, y, r, 0, Math.PI * 2); 1092 | this.context.stroke(); 1093 | return this; 1094 | }, 1095 | 1096 | circle: function(x, y, r) { 1097 | this.context.beginPath(); 1098 | this.context.arc(x, y, r, 0, Math.PI * 2); 1099 | return this; 1100 | }, 1101 | 1102 | crop: function(x, y, w, h) { 1103 | 1104 | if (arguments.length === 1) { 1105 | 1106 | var y = arguments[0][1]; 1107 | var w = arguments[0][2]; 1108 | var h = arguments[0][3]; 1109 | var x = arguments[0][0]; 1110 | } 1111 | 1112 | var canvas = cq.createCanvas(w, h); 1113 | var context = canvas.getContext("2d"); 1114 | 1115 | context.drawImage(this.canvas, x, y, w, h, 0, 0, w, h); 1116 | this.canvas.width = w; 1117 | this.canvas.height = h; 1118 | 1119 | cq.setContextSmoothing(this.context, false); 1120 | 1121 | this.clear(); 1122 | this.context.drawImage(canvas, 0, 0); 1123 | 1124 | return this; 1125 | }, 1126 | 1127 | set: function(properties) { 1128 | 1129 | cq.extend(this.context, properties); 1130 | 1131 | }, 1132 | 1133 | resize: function(width, height) { 1134 | 1135 | var w = width, 1136 | h = height; 1137 | 1138 | if (arguments.length === 1) { 1139 | 1140 | w = arguments[0] * this.canvas.width | 0; 1141 | h = arguments[0] * this.canvas.height | 0; 1142 | 1143 | } else { 1144 | 1145 | if (height === false) { 1146 | 1147 | if (this.canvas.width > width) { 1148 | 1149 | h = this.canvas.height * (width / this.canvas.width) | 0; 1150 | w = width; 1151 | 1152 | } else { 1153 | 1154 | w = this.canvas.width; 1155 | h = this.canvas.height; 1156 | 1157 | } 1158 | 1159 | } else if (width === false) { 1160 | 1161 | if (this.canvas.width > width) { 1162 | 1163 | w = this.canvas.width * (height / this.canvas.height) | 0; 1164 | h = height; 1165 | 1166 | } else { 1167 | 1168 | w = this.canvas.width; 1169 | h = this.canvas.height; 1170 | 1171 | } 1172 | 1173 | } 1174 | 1175 | } 1176 | 1177 | var cqresized = cq(w, h).drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, w, h); 1178 | 1179 | this.canvas = cqresized.canvas; 1180 | this.context = cqresized.context; 1181 | 1182 | return this; 1183 | 1184 | }, 1185 | 1186 | resizeBounds: function(width, height) { 1187 | 1188 | var temp = cq(width, height); 1189 | 1190 | temp.drawImage(this.canvas, 0, 0); 1191 | 1192 | this.canvas = temp.canvas; 1193 | this.context = temp.context; 1194 | 1195 | return this; 1196 | 1197 | }, 1198 | 1199 | imageLine: function(image, region, x, y, ex, ey, scale) { 1200 | 1201 | if (!region) region = [0, 0, image.width, image.height]; 1202 | 1203 | var distance = cq.distance(x, y, ex, ey); 1204 | var count = distance / region[3] + 0.5 | 0; 1205 | var angle = Math.atan2(ey - y, ex - x) + Math.PI / 2; 1206 | 1207 | this.save(); 1208 | 1209 | this.translate(x, y); 1210 | this.rotate(angle); 1211 | 1212 | if (scale) this.scale(scale, 1.0); 1213 | 1214 | for (var i = 0; i <= count; i++) { 1215 | this.drawRegion(image, region, -region[2] / 2 | 0, -region[3] * (i + 1)); 1216 | } 1217 | 1218 | this.restore(); 1219 | 1220 | return this; 1221 | 1222 | }, 1223 | 1224 | trim: function(color, changes) { 1225 | var transparent; 1226 | 1227 | if (color) { 1228 | color = cq.color(color).toArray(); 1229 | transparent = !color[3]; 1230 | } else transparent = true; 1231 | 1232 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1233 | var sourcePixels = sourceData.data; 1234 | 1235 | var bound = [this.canvas.width, this.canvas.height, 0, 0]; 1236 | 1237 | var width = this.canvas.width; 1238 | var height = this.canvas.height; 1239 | 1240 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 1241 | if (transparent) { 1242 | if (!sourcePixels[i + 3]) continue; 1243 | } else if (sourcePixels[i + 0] === color[0] && sourcePixels[i + 1] === color[1] && sourcePixels[i + 2] === color[2]) continue; 1244 | 1245 | var x = (i / 4 | 0) % this.canvas.width | 0; 1246 | var y = (i / 4 | 0) / this.canvas.width | 0; 1247 | 1248 | if (x < bound[0]) bound[0] = x; 1249 | if (x > bound[2]) bound[2] = x; 1250 | 1251 | if (y < bound[1]) bound[1] = y; 1252 | if (y > bound[3]) bound[3] = y; 1253 | } 1254 | 1255 | 1256 | if (bound[2] === 0 && bound[3] === 0) {} else { 1257 | if (changes) { 1258 | changes.left = bound[0]; 1259 | changes.top = bound[1]; 1260 | 1261 | changes.bottom = height - bound[3]; 1262 | changes.right = width - bound[2] - bound[0]; 1263 | 1264 | changes.width = bound[2] - bound[0]; 1265 | changes.height = bound[3] - bound[1]; 1266 | } 1267 | 1268 | this.crop(bound[0], bound[1], bound[2] - bound[0] + 1, bound[3] - bound[1] + 1); 1269 | } 1270 | 1271 | return this; 1272 | }, 1273 | 1274 | matchPalette: function(palette) { 1275 | 1276 | if (!palette.matches) palette.matches = new Map; 1277 | 1278 | if (!palette.colors) { 1279 | 1280 | palette.colors = []; 1281 | 1282 | for (var i = 0; i < palette.length; i++) { 1283 | 1284 | palette.colors.push(cq.color(palette[i])); 1285 | 1286 | } 1287 | } 1288 | 1289 | var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1290 | var pixels = imageData.data; 1291 | 1292 | for (var i = 0; i < pixels.length; i += 4) { 1293 | 1294 | var difList = []; 1295 | 1296 | if (!pixels[i + 3]) continue; 1297 | 1298 | var key = 1299 | (pixels[i + 0] / cq.matchPalettePrecision | 0) * cq.matchPalettePrecision + 1300 | (pixels[i + 1] / cq.matchPalettePrecision | 0) * cq.matchPalettePrecision * 1000 + 1301 | (pixels[i + 2] / cq.matchPalettePrecision | 0) * cq.matchPalettePrecision * 1000000; 1302 | 1303 | 1304 | if (!palette.matches.has(key)) { 1305 | 1306 | for (var j = 0; j < palette.colors.length; j++) { 1307 | 1308 | var rgb = palette.colors[j]; 1309 | var rDif = Math.abs(pixels[i] - rgb[0]); 1310 | var gDif = Math.abs(pixels[i + 1] - rgb[1]) 1311 | var bDif = Math.abs(pixels[i + 2] - rgb[2]); 1312 | 1313 | difList.push(rDif + gDif + bDif); 1314 | 1315 | } 1316 | 1317 | var closestMatch = 0; 1318 | 1319 | for (var j = 0; j < palette.length; j++) { 1320 | 1321 | if (difList[j] < difList[closestMatch]) { 1322 | 1323 | closestMatch = j; 1324 | 1325 | } 1326 | 1327 | } 1328 | 1329 | palette.matches.set(key, palette.colors[closestMatch]); 1330 | 1331 | } 1332 | 1333 | var matchedColor = palette.matches.get(key); 1334 | 1335 | pixels[i] = matchedColor[0]; 1336 | pixels[i + 1] = matchedColor[1]; 1337 | pixels[i + 2] = matchedColor[2]; 1338 | 1339 | /* dithering */ 1340 | 1341 | //imageData.data[i + 3] = (255 * Math.random() < imageData.data[i + 3]) ? 255 : 0; 1342 | 1343 | //imageData.data[i + 3] = imageData.data[i + 3] > 128 ? 255 : 0; 1344 | /* 1345 | if (i % 3 === 0) { 1346 | imageData.data[i] -= cq.limitValue(imageData.data[i] - 50, 0, 255); 1347 | imageData.data[i + 1] -= cq.limitValue(imageData.data[i + 1] - 50, 0, 255); 1348 | imageData.data[i + 2] -= cq.limitValue(imageData.data[i + 2] - 50, 0, 255); 1349 | } 1350 | */ 1351 | 1352 | } 1353 | 1354 | this.context.putImageData(imageData, 0, 0); 1355 | 1356 | return this; 1357 | 1358 | }, 1359 | 1360 | swapColors: function(colors) { 1361 | 1362 | var colormap = {}; 1363 | 1364 | for (var key in colors) { 1365 | 1366 | var color = cq.color(key); 1367 | var index = color[0] + color[1] * 1000 + color[2] * 1000000; 1368 | // var index = String(color[0]) + "," + String(color[1]) + "," + String(color[2]); 1369 | 1370 | colormap[index] = cq.color(colors[key]); 1371 | 1372 | } 1373 | 1374 | var imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1375 | var pixels = imageData.data; 1376 | 1377 | for (var i = 0; i < pixels.length; i += 4) { 1378 | 1379 | if (!pixels[i + 3]) continue; 1380 | 1381 | var index = pixels[i] + pixels[i + 1] * 1000 + pixels[i + 2] * 1000000; 1382 | // var index = String(pixels[i + 0]) + "," + String(pixels[i + 1]) + "," + String(pixels[i + 2]); 1383 | 1384 | if (colormap[index]) { 1385 | 1386 | pixels[i] = colormap[index][0]; 1387 | pixels[i + 1] = colormap[index][1]; 1388 | pixels[i + 2] = colormap[index][2]; 1389 | 1390 | } 1391 | 1392 | } 1393 | 1394 | this.context.putImageData(imageData, 0, 0); 1395 | 1396 | return this; 1397 | 1398 | }, 1399 | 1400 | getPalette: function() { 1401 | 1402 | var palette = []; 1403 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1404 | var sourcePixels = sourceData.data; 1405 | 1406 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 1407 | if (sourcePixels[i + 3]) { 1408 | var hex = cq.rgbToHex(sourcePixels[i + 0], sourcePixels[i + 1], sourcePixels[i + 2]); 1409 | if (palette.indexOf(hex) === -1) palette.push(hex); 1410 | } 1411 | } 1412 | 1413 | return palette; 1414 | }, 1415 | 1416 | mapPalette: function() { 1417 | 1418 | }, 1419 | 1420 | polygon: function(array, x, y) { 1421 | 1422 | if (x === undefined) { 1423 | x = 0; 1424 | } 1425 | if (y === undefined) { 1426 | y = 0; 1427 | } 1428 | 1429 | this.beginPath(); 1430 | 1431 | this.moveTo(array[0][0] + x, array[0][1] + y); 1432 | 1433 | for (var i = 1; i < array.length; i++) { 1434 | this.lineTo(array[i][0] + x, array[i][1] + y); 1435 | } 1436 | 1437 | this.closePath(); 1438 | 1439 | return this; 1440 | 1441 | }, 1442 | 1443 | fillPolygon: function(polygon) { 1444 | 1445 | this.polygon(polygon); 1446 | this.fill(); 1447 | 1448 | }, 1449 | 1450 | strokePolygon: function(polygon) { 1451 | 1452 | this.polygon(polygon); 1453 | this.stroke(); 1454 | 1455 | }, 1456 | 1457 | colorToMask: function(color, inverted) { 1458 | color = cq.color(color).toArray(); 1459 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1460 | var sourcePixels = sourceData.data; 1461 | 1462 | var mask = []; 1463 | 1464 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 1465 | if (sourcePixels[i + 3] > 0) mask.push(inverted ? false : true); 1466 | else mask.push(inverted ? true : false); 1467 | } 1468 | 1469 | return mask; 1470 | }, 1471 | 1472 | grayscaleToMask: function() { 1473 | 1474 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1475 | var sourcePixels = sourceData.data; 1476 | 1477 | var mask = []; 1478 | 1479 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 1480 | mask.push(((sourcePixels[i + 0] + sourcePixels[i + 1] + sourcePixels[i + 2]) / 3) / 255); 1481 | } 1482 | 1483 | return mask; 1484 | }, 1485 | 1486 | applyMask: function(mask) { 1487 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1488 | var sourcePixels = sourceData.data; 1489 | 1490 | var mode = typeof mask[0] === "boolean" ? "bool" : "byte"; 1491 | 1492 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 1493 | var value = mask[i / 4]; 1494 | sourcePixels[i + 3] = value * 255 | 0; 1495 | } 1496 | 1497 | this.context.putImageData(sourceData, 0, 0); 1498 | return this; 1499 | }, 1500 | 1501 | fillMask: function(mask) { 1502 | 1503 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1504 | var sourcePixels = sourceData.data; 1505 | 1506 | var maskType = typeof mask[0] === "boolean" ? "bool" : "byte"; 1507 | var colorMode = arguments.length === 2 ? "normal" : "gradient"; 1508 | 1509 | var color = cq.color(arguments[1]); 1510 | if (colorMode === "gradient") colorB = cq.color(arguments[2]); 1511 | 1512 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 1513 | var value = mask[i / 4]; 1514 | 1515 | if (maskType === "byte") value /= 255; 1516 | 1517 | if (colorMode === "normal") { 1518 | if (value) { 1519 | sourcePixels[i + 0] = color[0] | 0; 1520 | sourcePixels[i + 1] = color[1] | 0; 1521 | sourcePixels[i + 2] = color[2] | 0; 1522 | sourcePixels[i + 3] = value * 255 | 0; 1523 | } 1524 | } else { 1525 | sourcePixels[i + 0] = color[0] + (colorB[0] - color[0]) * value | 0; 1526 | sourcePixels[i + 1] = color[1] + (colorB[1] - color[1]) * value | 0; 1527 | sourcePixels[i + 2] = color[2] + (colorB[2] - color[2]) * value | 0; 1528 | sourcePixels[i + 3] = 255; 1529 | } 1530 | } 1531 | 1532 | this.context.putImageData(sourceData, 0, 0); 1533 | return this; 1534 | }, 1535 | 1536 | clear: function(color) { 1537 | if (color) { 1538 | this.context.fillStyle = color; 1539 | this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); 1540 | } else { 1541 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); 1542 | } 1543 | 1544 | return this; 1545 | }, 1546 | 1547 | clone: function() { 1548 | 1549 | // var result = cq.createCanvas(this.canvas); 1550 | 1551 | var result = cq.pool(); 1552 | result.width = this.width; 1553 | result.height = this.height; 1554 | result.getContext("2d").drawImage(this.canvas, 0, 0); 1555 | 1556 | return cq(result); 1557 | }, 1558 | 1559 | gradientText: function(text, x, y, maxWidth, gradient) { 1560 | 1561 | var words = text.split(" "); 1562 | 1563 | var h = this.fontHeight() * 2; 1564 | 1565 | var ox = 0; 1566 | var oy = 0; 1567 | 1568 | if (maxWidth) { 1569 | var line = 0; 1570 | var lines = [""]; 1571 | 1572 | for (var i = 0; i < words.length; i++) { 1573 | var word = words[i] + " "; 1574 | var wordWidth = this.context.measureText(word).width; 1575 | 1576 | if (ox + wordWidth > maxWidth) { 1577 | lines[++line] = ""; 1578 | ox = 0; 1579 | } 1580 | 1581 | lines[line] += word; 1582 | 1583 | ox += wordWidth; 1584 | } 1585 | } else var lines = [text]; 1586 | 1587 | for (var i = 0; i < lines.length; i++) { 1588 | var oy = y + i * h * 0.6 | 0; 1589 | var lingrad = this.context.createLinearGradient(0, oy, 0, oy + h * 0.6 | 0); 1590 | 1591 | for (var j = 0; j < gradient.length; j += 2) { 1592 | lingrad.addColorStop(gradient[j], gradient[j + 1]); 1593 | } 1594 | 1595 | var text = lines[i]; 1596 | 1597 | this.fillStyle(lingrad).fillText(text, x, oy); 1598 | } 1599 | 1600 | return this; 1601 | }, 1602 | 1603 | removeColor: function(color) { 1604 | 1605 | color = cq.color(color); 1606 | 1607 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1608 | var pixels = data.data; 1609 | 1610 | for (var x = 0; x < this.canvas.width; x++) { 1611 | for (var y = 0; y < this.canvas.height; y++) { 1612 | var i = (y * this.canvas.width + x) * 4; 1613 | 1614 | if (pixels[i + 0] === color[0] && pixels[i + 1] === color[1] && pixels[i + 2] === color[2]) { 1615 | pixels[i + 3] = 0; 1616 | } 1617 | 1618 | 1619 | } 1620 | } 1621 | 1622 | this.clear(); 1623 | this.context.putImageData(data, 0, 0); 1624 | 1625 | return this; 1626 | }, 1627 | 1628 | outline: function() { 1629 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1630 | var pixels = data.data; 1631 | 1632 | var newData = this.createImageData(this.canvas.width, this.canvas.height); 1633 | var newPixels = newData.data; 1634 | 1635 | var canvas = this.canvas; 1636 | 1637 | function check(x, y) { 1638 | 1639 | if (x < 0) return 0; 1640 | if (x >= canvas.width) return 0; 1641 | if (y < 0) return 0; 1642 | if (y >= canvas.height) return 0; 1643 | 1644 | var i = (x + y * canvas.width) * 4; 1645 | 1646 | return pixels[i + 3] > 0; 1647 | 1648 | } 1649 | 1650 | for (var x = 0; x < this.canvas.width; x++) { 1651 | for (var y = 0; y < this.canvas.height; y++) { 1652 | 1653 | var full = 0; 1654 | var i = (y * canvas.width + x) * 4; 1655 | 1656 | if (!pixels[i + 3]) continue; 1657 | 1658 | full += check(x - 1, y); 1659 | full += check(x + 1, y); 1660 | full += check(x, y - 1); 1661 | full += check(x, y + 1); 1662 | 1663 | if (full !== 4) { 1664 | 1665 | newPixels[i] = 255; 1666 | newPixels[i + 1] = 255; 1667 | newPixels[i + 2] = 255; 1668 | newPixels[i + 3] = 255; 1669 | } 1670 | 1671 | } 1672 | } 1673 | 1674 | this.context.putImageData(newData, 0, 0); 1675 | 1676 | return this; 1677 | }, 1678 | 1679 | setHsl: function() { 1680 | 1681 | if (arguments.length === 1) { 1682 | var args = arguments[0]; 1683 | } else { 1684 | var args = arguments; 1685 | } 1686 | 1687 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1688 | var pixels = data.data; 1689 | var r, g, b, a, h, s, l, hsl = [], 1690 | newPixel = []; 1691 | 1692 | for (var i = 0, len = pixels.length; i < len; i += 4) { 1693 | hsl = cq.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); 1694 | 1695 | h = args[0] === false ? hsl[0] : cq.limitValue(args[0], 0, 1); 1696 | s = args[1] === false ? hsl[1] : cq.limitValue(args[1], 0, 1); 1697 | l = args[2] === false ? hsl[2] : cq.limitValue(args[2], 0, 1); 1698 | 1699 | newPixel = cq.hslToRgb(h, s, l); 1700 | 1701 | pixels[i + 0] = newPixel[0]; 1702 | pixels[i + 1] = newPixel[1]; 1703 | pixels[i + 2] = newPixel[2]; 1704 | } 1705 | 1706 | this.context.putImageData(data, 0, 0); 1707 | 1708 | return this; 1709 | }, 1710 | 1711 | shiftHsl: function() { 1712 | 1713 | if (arguments.length === 1) { 1714 | var args = arguments[0]; 1715 | } else { 1716 | var args = arguments; 1717 | } 1718 | 1719 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1720 | var pixels = data.data; 1721 | var r, g, b, a, h, s, l, hsl = [], 1722 | newPixel = []; 1723 | 1724 | for (var i = 0, len = pixels.length; i < len; i += 4) { 1725 | hsl = cq.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); 1726 | 1727 | if (pixels[i + 0] !== pixels[i + 1] || pixels[i + 1] !== pixels[i + 2]) { 1728 | h = args[0] === false ? hsl[0] : cq.wrapValue(hsl[0] + args[0], 0, 1); 1729 | s = args[1] === false ? hsl[1] : cq.limitValue(hsl[1] + args[1], 0, 1); 1730 | } else { 1731 | h = hsl[0]; 1732 | s = hsl[1]; 1733 | } 1734 | 1735 | l = args[2] === false ? hsl[2] : cq.limitValue(hsl[2] + args[2], 0, 1); 1736 | 1737 | newPixel = cq.hslToRgb(h, s, l); 1738 | 1739 | pixels[i + 0] = newPixel[0]; 1740 | pixels[i + 1] = newPixel[1]; 1741 | pixels[i + 2] = newPixel[2]; 1742 | } 1743 | 1744 | 1745 | this.context.putImageData(data, 0, 0); 1746 | 1747 | return this; 1748 | }, 1749 | 1750 | applyColor: function(color) { 1751 | 1752 | if (COCOONJS) return this; 1753 | this.save(); 1754 | 1755 | this.globalCompositeOperation("source-in"); 1756 | this.clear(color); 1757 | 1758 | this.restore(); 1759 | 1760 | return this; 1761 | }, 1762 | 1763 | negative: function(src, dst) { 1764 | 1765 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1766 | var pixels = data.data; 1767 | var r, g, b, a, h, s, l, hsl = [], 1768 | newPixel = []; 1769 | 1770 | for (var i = 0, len = pixels.length; i < len; i += 4) { 1771 | pixels[i + 0] = 255 - pixels[i + 0]; 1772 | pixels[i + 1] = 255 - pixels[i + 1]; 1773 | pixels[i + 2] = 255 - pixels[i + 2]; 1774 | } 1775 | 1776 | this.context.putImageData(data, 0, 0); 1777 | 1778 | return this; 1779 | }, 1780 | 1781 | roundRect: function(x, y, width, height, radius) { 1782 | 1783 | this.beginPath(); 1784 | this.moveTo(x + radius, y); 1785 | this.lineTo(x + width - radius, y); 1786 | this.quadraticCurveTo(x + width, y, x + width, y + radius); 1787 | this.lineTo(x + width, y + height - radius); 1788 | this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 1789 | this.lineTo(x + radius, y + height); 1790 | this.quadraticCurveTo(x, y + height, x, y + height - radius); 1791 | this.lineTo(x, y + radius); 1792 | this.quadraticCurveTo(x, y, x + radius, y); 1793 | this.closePath(); 1794 | 1795 | return this; 1796 | }, 1797 | 1798 | markupText: function(text) { 1799 | 1800 | 1801 | }, 1802 | 1803 | charWidth: function(char) { 1804 | 1805 | if (!cq.charWidthCache) cq.charWidthCache = new Map(); 1806 | 1807 | if (!cq.charWidthCache.has(this.context.font + char)) { 1808 | 1809 | var width = this.measureText(char).width; 1810 | 1811 | cq.charWidthCache.set(this.context.font + char, width); 1812 | 1813 | } 1814 | 1815 | return cq.charWidthCache.get(this.context.font + char); 1816 | 1817 | }, 1818 | 1819 | pixelText: function(text, x, y) { 1820 | 1821 | var prevTextAlign = this.context.textAlign; 1822 | 1823 | this.context.textAlign = "left"; 1824 | 1825 | var textWidth = 0; 1826 | 1827 | if (prevTextAlign === "center") { 1828 | 1829 | for (var i = 0; i < text.length; i++) { 1830 | 1831 | var w = this.charWidth(text[i]); 1832 | 1833 | var o = w - (w | 0); 1834 | 1835 | textWidth += w + (o > 0.5 ? 1 : 0) | 0; 1836 | 1837 | } 1838 | 1839 | x -= textWidth / 2 | 0; 1840 | 1841 | } 1842 | 1843 | for (var i = 0; i < text.length; i++) { 1844 | 1845 | var c = text[i]; 1846 | 1847 | var w = this.charWidth(c); 1848 | 1849 | var o = w - (w | 0); 1850 | 1851 | this.context.fillText(c, x, y); 1852 | 1853 | x += w + (o > 0.5 ? 1 : 0) | 0; 1854 | 1855 | } 1856 | 1857 | this.context.textAlign = prevTextAlign; 1858 | 1859 | return this; 1860 | 1861 | }, 1862 | 1863 | wrappedText: function(text, x, y, maxWidth, lineHeight) { 1864 | 1865 | 1866 | if (maxWidth < 0) maxWidth = 0; 1867 | 1868 | var words = text.split(" "); 1869 | 1870 | var lineHeight = lineHeight || this.fontHeight(); 1871 | 1872 | var ox = 0; 1873 | var oy = 0; 1874 | 1875 | var textAlign = this.context.textAlign; 1876 | var textBaseline = this.context.textBaseline; 1877 | 1878 | this.textBaseline("top"); 1879 | 1880 | var spaceWidth = this.context.measureText(" ").width | 0; 1881 | // var newlineOnly = !maxWidth && text.indexOf("\n") > -1; 1882 | 1883 | if (!maxWidth && text.indexOf("\n") > -1) { 1884 | 1885 | maxWidth = this.textBoundaries(text).width; 1886 | 1887 | } 1888 | 1889 | if (maxWidth) { 1890 | 1891 | var line = 0; 1892 | var lines = [""]; 1893 | var linesWidth = [0]; 1894 | 1895 | for (var i = 0; i < words.length; i++) { 1896 | 1897 | var word = words[i]; 1898 | 1899 | var wordWidth = Math.ceil(this.context.measureText(word).width); 1900 | 1901 | if (maxWidth && wordWidth > maxWidth) { 1902 | 1903 | /* 4 is still risky, it's valid as long as `-` is the delimiter */ 1904 | 1905 | if (word.length <= 5) return; 1906 | 1907 | var split = word.length / 2 | 0; 1908 | 1909 | words.splice(i, 1); 1910 | words.splice(i, 0, "-" + word.substr(split)); 1911 | words.splice(i, 0, word.substr(0, split) + "-"); 1912 | 1913 | i--; 1914 | 1915 | continue; 1916 | } 1917 | 1918 | if ((ox + wordWidth > maxWidth) || words[i] === "\n") { 1919 | 1920 | lines[line] = lines[line].substr(0, lines[line].length - 1); 1921 | linesWidth[line] -= spaceWidth; 1922 | 1923 | lines[++line] = ""; 1924 | linesWidth[line] = 0; 1925 | ox = 0; 1926 | } 1927 | 1928 | if (words[i] !== "\n") { 1929 | 1930 | lines[line] += word + " "; 1931 | 1932 | ox += wordWidth + spaceWidth; 1933 | 1934 | linesWidth[line] += wordWidth + spaceWidth; 1935 | 1936 | } 1937 | 1938 | } 1939 | 1940 | if (words[i] !== "\n") { 1941 | lines[line] = lines[line].substr(0, lines[line].length - 1); 1942 | linesWidth[line] -= spaceWidth; 1943 | } 1944 | 1945 | 1946 | } else { 1947 | 1948 | var lines = [text]; 1949 | var linesWidth = [this.context.measureText(text).width]; 1950 | 1951 | } 1952 | 1953 | for (var i = 0; i < lines.length; i++) { 1954 | 1955 | var oy = y + i * lineHeight | 0; 1956 | 1957 | var text = lines[i]; 1958 | var width = linesWidth[i]; 1959 | 1960 | this.textAlign("left"); 1961 | 1962 | if (textAlign === "left" || textAlign === "start") 1963 | this.fillText(text, x, oy); 1964 | else if (textAlign === "center") 1965 | this.fillText(text, x - width * 0.5 | 0, oy); 1966 | else 1967 | this.fillText(text, x - width, oy); 1968 | 1969 | } 1970 | 1971 | this.textAlign(textAlign); 1972 | this.textBaseline(textBaseline); 1973 | 1974 | return this; 1975 | 1976 | }, 1977 | 1978 | fontHeights: {}, 1979 | fontTops: {}, 1980 | 1981 | fontTop: function() { 1982 | 1983 | if (!this.fontTops[this.context.font]) this.fontHeight(); 1984 | 1985 | return this.fontTops[this.context.font]; 1986 | 1987 | }, 1988 | 1989 | fontHeight: function() { 1990 | 1991 | var font = this.font(); 1992 | 1993 | if (!this.fontHeights[font]) { 1994 | 1995 | var fontStyleHeight = parseInt(font); 1996 | 1997 | var temp = cq(100, 10 + fontStyleHeight * 2 | 0); 1998 | 1999 | cq.setContextSmoothing(temp.context, false); 2000 | 2001 | temp.font(font).fillStyle("#fff"); 2002 | temp.textBaseline("top"); 2003 | 2004 | /* Use direct fillText as internal inmplementation uses fontWidth() */ 2005 | var oy = 10; 2006 | 2007 | temp.context.fillText("Play Moog", 20, oy); 2008 | 2009 | var data = temp.getImageData(0, 0, temp.width, temp.height).data; 2010 | 2011 | var top = temp.height; 2012 | var bottom = 0; 2013 | 2014 | for (var i = 0; i < data.length; i += 4) { 2015 | 2016 | var x = (i / 4 | 0) % temp.width; 2017 | var y = (i / 4 | 0) / temp.width | 0; 2018 | 2019 | /* A little threshold for anti-alias */ 2020 | 2021 | if (data[i + 3] < 200) continue; 2022 | 2023 | if (y < top) top = y; 2024 | if (y > bottom) bottom = y; 2025 | 2026 | } 2027 | 2028 | this.fontHeights[font] = bottom - oy + 1; 2029 | this.fontTops[font] = top - oy; 2030 | 2031 | } 2032 | 2033 | return this.fontHeights[font]; 2034 | 2035 | }, 2036 | 2037 | textBoundaries: function(text, maxWidth) { 2038 | 2039 | if (maxWidth < 0) maxWidth = 0; 2040 | 2041 | var words = text.split(" "); 2042 | 2043 | var h = this.fontHeight(); 2044 | 2045 | var ox = 0; 2046 | var oy = 0; 2047 | 2048 | var spaceWidth = this.context.measureText(" ").width; 2049 | 2050 | var line = 0; 2051 | var lines = [""]; 2052 | 2053 | var width = 0; 2054 | 2055 | for (var i = 0; i < words.length; i++) { 2056 | 2057 | var word = words[i]; 2058 | var wordWidth = Math.ceil(this.context.measureText(word).width); 2059 | 2060 | if (maxWidth && (wordWidth > maxWidth)) { 2061 | 2062 | if (word.length <= 5) continue; 2063 | 2064 | var split = word.length / 2 | 0; 2065 | 2066 | words.splice(i, 1); 2067 | words.splice(i, 0, "-" + word.substr(split)); 2068 | words.splice(i, 0, word.substr(0, split) + "-"); 2069 | 2070 | i--; 2071 | 2072 | continue; 2073 | } 2074 | 2075 | if (((ox + wordWidth > maxWidth) && maxWidth) || words[i] === "\n") { 2076 | 2077 | if (ox > width) width = ox; 2078 | 2079 | lines[++line] = ""; 2080 | 2081 | ox = 0; 2082 | 2083 | } 2084 | 2085 | if (words[i] !== "\n") { 2086 | 2087 | lines[line] += word; 2088 | 2089 | ox += wordWidth + spaceWidth; 2090 | 2091 | } 2092 | 2093 | } 2094 | 2095 | if (maxWidth) { 2096 | 2097 | var width = maxWidth; 2098 | 2099 | } else { 2100 | 2101 | if (!width) { 2102 | 2103 | width = this.context.measureText(text).width; 2104 | 2105 | } 2106 | 2107 | } 2108 | 2109 | return { 2110 | height: lines.length * h, 2111 | width: Math.ceil(width), 2112 | lines: lines.length, 2113 | fontHeight: h 2114 | } 2115 | 2116 | }, 2117 | 2118 | repeatImageRegion: function(image, sx, sy, sw, sh, dx, dy, dw, dh) { 2119 | this.save(); 2120 | this.rect(dx, dy, dw, dh); 2121 | this.clip(); 2122 | 2123 | for (var x = 0, len = Math.ceil(dw / sw); x < len; x++) { 2124 | for (var y = 0, leny = Math.ceil(dh / sh); y < leny; y++) { 2125 | this.drawImage(image, sx, sy, sw, sh, dx + x * sw, dy + y * sh, sw, sh); 2126 | } 2127 | } 2128 | 2129 | this.restore(); 2130 | 2131 | return this; 2132 | }, 2133 | 2134 | repeatImage: function(image, x, y, w, h) { 2135 | // if (!env.details) return this; 2136 | 2137 | if (arguments.length < 9) { 2138 | 2139 | this.repeatImageRegion(image, 0, 0, image.width, image.height, x, y, w, h); 2140 | 2141 | } else { 2142 | 2143 | this.repeatImageRegion.apply(this, arguments); 2144 | 2145 | } 2146 | 2147 | return this; 2148 | }, 2149 | 2150 | borderImageEmptyRegion: [0, 0, 0, 0], 2151 | 2152 | borderImage: function(image, x, y, w, h, t, r, b, l, fill) { 2153 | 2154 | // if (!env.details) return this; 2155 | 2156 | if (typeof t === "object") { 2157 | 2158 | var region = t.region; 2159 | 2160 | if (!region) { 2161 | 2162 | region = this.borderImageEmptyRegion; 2163 | region[2] = image.width; 2164 | region[3] = image.height; 2165 | 2166 | } 2167 | 2168 | if (t.outset) { 2169 | 2170 | var outset = t.outset; 2171 | 2172 | if (w > outset * 2 && h > outset * 2) { 2173 | 2174 | if (t.fill !== false) { 2175 | 2176 | this.drawImage(image, 2177 | region[0] + outset, 2178 | region[1] + outset, (region[2] - outset * 2), (region[3] - outset * 2), 2179 | x + outset, y + outset, 2180 | w - outset * 2, 2181 | h - outset * 2 2182 | ); 2183 | 2184 | } 2185 | 2186 | 2187 | /* edges */ 2188 | 2189 | this.drawImage(image, region[0], region[1] + outset, outset, region[3] - 2 * outset, x, y + outset, outset, h - outset * 2); 2190 | this.drawImage(image, region[0] + region[2] - outset, region[1] + outset, outset, region[3] - 2 * outset, x + w - outset, y + outset, outset, h - outset * 2); 2191 | this.drawImage(image, region[0] + outset, region[1], region[2] - outset * 2, outset, x + outset, y, w - outset * 2, outset); 2192 | this.drawImage(image, region[0] + outset, region[1] + region[3] - outset, region[2] - outset * 2, outset, x + outset, y + h - outset, w - outset * 2, outset); 2193 | 2194 | /* corners */ 2195 | 2196 | this.drawImage(image, region[0], region[1], outset, outset, x, y, outset, outset); 2197 | this.drawImage(image, region[0], region[1] + region[3] - outset, outset, outset, x, y + h - outset, outset, outset); 2198 | this.drawImage(image, region[0] + region[2] - outset, region[1], outset, outset, x + w - outset, y, outset, outset); 2199 | this.drawImage(image, region[0] + region[2] - outset, region[1] + region[3] - outset, outset, outset, x + w - outset, y + h - outset, outset, outset); 2200 | 2201 | } 2202 | 2203 | } 2204 | 2205 | /* complex */ 2206 | else { 2207 | 2208 | var bottomLeft = t.bottomLeft || [0, 0, 0, 0]; 2209 | var bottomRight = t.bottomRight || [0, 0, 0, 0]; 2210 | var topLeft = t.topLeft || [0, 0, 0, 0]; 2211 | var topRight = t.topRight || [0, 0, 0, 0]; 2212 | 2213 | var clh = bottomLeft[3] + topLeft[3]; 2214 | var crh = bottomRight[3] + topRight[3]; 2215 | var ctw = topLeft[2] + topRight[2]; 2216 | var cbw = bottomLeft[2] + bottomRight[2]; 2217 | 2218 | t.fillPadding = [0, 0, 0, 0]; 2219 | 2220 | if (t.left) t.fillPadding[0] = t.left[2]; 2221 | if (t.top) t.fillPadding[1] = t.top[3]; 2222 | if (t.right) t.fillPadding[2] = t.right[2]; 2223 | if (t.bottom) t.fillPadding[3] = t.bottom[3]; 2224 | 2225 | // if (!t.fillPadding) t.fillPadding = [0, 0, 0, 0]; 2226 | 2227 | if (t.fill) { 2228 | this.drawImage(image, t.fill[0], t.fill[1], t.fill[2], t.fill[3], x + t.fillPadding[0], y + t.fillPadding[1], w - t.fillPadding[2] - t.fillPadding[0], h - t.fillPadding[3] - t.fillPadding[1]); 2229 | } else { 2230 | // this.fillRect(x + t.fillPadding[0], y + t.fillPadding[1], w - t.fillPadding[2] - t.fillPadding[0], h - t.fillPadding[3] - t.fillPadding[1]); 2231 | } 2232 | 2233 | /* sides */ 2234 | 2235 | if (t.left) this[t.left[4] === "stretch" ? "drawImage" : "repeatImage"](image, t.left[0], t.left[1], t.left[2], t.left[3], x, y + topLeft[3], t.left[2], h - clh); 2236 | if (t.right) this[t.right[4] === "stretch" ? "drawImage" : "repeatImage"](image, t.right[0], t.right[1], t.right[2], t.right[3], x + w - t.right[2], y + topRight[3], t.right[2], h - crh); 2237 | if (t.top) this[t.top[4] === "stretch" ? "drawImage" : "repeatImage"](image, t.top[0], t.top[1], t.top[2], t.top[3], x + topLeft[2], y, w - ctw, t.top[3]); 2238 | if (t.bottom) this[t.bottom[4] === "stretch" ? "drawImage" : "repeatImage"](image, t.bottom[0], t.bottom[1], t.bottom[2], t.bottom[3], x + bottomLeft[2], y + h - t.bottom[3], w - cbw, t.bottom[3]); 2239 | 2240 | /* corners */ 2241 | 2242 | if (t.bottomLeft) this.drawImage(image, t.bottomLeft[0], t.bottomLeft[1], t.bottomLeft[2], t.bottomLeft[3], x, y + h - t.bottomLeft[3], t.bottomLeft[2], t.bottomLeft[3]); 2243 | if (t.topLeft) this.drawImage(image, t.topLeft[0], t.topLeft[1], t.topLeft[2], t.topLeft[3], x, y, t.topLeft[2], t.topLeft[3]); 2244 | if (t.topRight) this.drawImage(image, t.topRight[0], t.topRight[1], t.topRight[2], t.topRight[3], x + w - t.topRight[2], y, t.topRight[2], t.topRight[3]); 2245 | if (t.bottomRight) this.drawImage(image, t.bottomRight[0], t.bottomRight[1], t.bottomRight[2], t.bottomRight[3], x + w - t.bottomRight[2], y + h - t.bottomRight[3], t.bottomRight[2], t.bottomRight[3]); 2246 | 2247 | } 2248 | 2249 | } else { 2250 | 2251 | 2252 | /* top */ 2253 | if (t > 0 && w - l - r > 0) this.drawImage(image, l, 0, image.width - l - r, t, x + l, y, w - l - r, t); 2254 | 2255 | /* bottom */ 2256 | if (b > 0 && w - l - r > 0) this.drawImage(image, l, image.height - b, image.width - l - r, b, x + l, y + h - b, w - l - r, b); 2257 | // console.log(x, y, w, h, t, r, b, l); 2258 | // console.log(image, 0, t, l, image.height - b - t, x, y + t, l, h - b - t); 2259 | /* left */ 2260 | if (l > 0 && h - b - t > 0) this.drawImage(image, 0, t, l, image.height - b - t, x, y + t, l, h - b - t); 2261 | 2262 | 2263 | /* right */ 2264 | if (r > 0 && h - b - t > 0) this.drawImage(image, image.width - r, t, r, image.height - b - t, x + w - r, y + t, r, h - b - t); 2265 | 2266 | /* top-left */ 2267 | if (l > 0 && t > 0) this.drawImage(image, 0, 0, l, t, x, y, l, t); 2268 | 2269 | /* top-right */ 2270 | if (r > 0 && t > 0) this.drawImage(image, image.width - r, 0, r, t, x + w - r, y, r, t); 2271 | 2272 | /* bottom-right */ 2273 | if (r > 0 && b > 0) this.drawImage(image, image.width - r, image.height - b, r, b, x + w - r, y + h - b, r, b); 2274 | 2275 | /* bottom-left */ 2276 | if (l > 0 && b > 0) this.drawImage(image, 0, image.height - b, l, b, x, y + h - b, l, b); 2277 | 2278 | if (fill) { 2279 | if (typeof fill === "string") { 2280 | this.fillStyle(fill).fillRect(x + l, y + t, w - l - r, h - t - b); 2281 | } else { 2282 | if (w - l - r > 0 && h - t - b > 0) 2283 | this.drawImage(image, l, t, image.width - r - l, image.height - b - t, x + l, y + t, w - l - r, h - t - b); 2284 | } 2285 | } 2286 | } 2287 | }, 2288 | 2289 | setPixel: function(color, x, y) { 2290 | 2291 | /* fillRect is slow! */ 2292 | 2293 | return this.fillStyle(color).fillRect(x, y, 1, 1); 2294 | 2295 | /* this is how it should work - but it does not */ 2296 | 2297 | color = cq.color(color); 2298 | 2299 | var pixel = this.createImageData(1, 1); 2300 | 2301 | pixel.data[0] = color[0]; 2302 | pixel.data[1] = color[1]; 2303 | pixel.data[2] = color[2]; 2304 | pixel.data[3] = 255; 2305 | 2306 | this.putImageData(pixel, x, y); 2307 | 2308 | return this; 2309 | }, 2310 | 2311 | getPixel: function(x, y) { 2312 | 2313 | var pixel = this.context.getImageData(x, y, 1, 1).data; 2314 | 2315 | return cq.color([pixel[0], pixel[1], pixel[2], pixel[3]]); 2316 | 2317 | }, 2318 | 2319 | clearRect: function(x, y, w, h) { 2320 | 2321 | this.context.clearRect(x, y, w, h); 2322 | 2323 | return this; 2324 | 2325 | }, 2326 | 2327 | fill: function() { 2328 | 2329 | this.context.fill(); 2330 | 2331 | return this; 2332 | 2333 | }, 2334 | 2335 | stroke: function() { 2336 | 2337 | this.context.stroke(); 2338 | 2339 | return this; 2340 | 2341 | }, 2342 | 2343 | createImageData: function(width, height) { 2344 | 2345 | if (false && this.context.createImageData) { 2346 | 2347 | return this.context.createImageData.apply(this.context, arguments); 2348 | 2349 | } else { 2350 | 2351 | if (!this.emptyCanvas) { 2352 | 2353 | this.emptyCanvas = cq.createCanvas(width, height); 2354 | this.emptyCanvasContext = this.emptyCanvas.getContext("2d"); 2355 | 2356 | } 2357 | 2358 | this.emptyCanvas.width = width; 2359 | this.emptyCanvas.height = height; 2360 | 2361 | return this.emptyCanvasContext.getImageData(0, 0, width, height); 2362 | } 2363 | 2364 | }, 2365 | 2366 | strokeLine: function(x1, y1, x2, y2) { 2367 | 2368 | this.beginPath(); 2369 | 2370 | if (typeof x2 === "undefined") { 2371 | this.moveTo(x1.x, x1.y); 2372 | this.lineTo(y1.x, y1.y); 2373 | } else { 2374 | this.moveTo(x1, y1); 2375 | this.lineTo(x2, y2); 2376 | } 2377 | 2378 | this.stroke(); 2379 | 2380 | return this; 2381 | 2382 | }, 2383 | 2384 | shadowOffset: function(x, y) { 2385 | 2386 | this.context.shadowOffsetX = x; 2387 | this.context.shadowOffsetY = y; 2388 | 2389 | return this; 2390 | 2391 | }, 2392 | 2393 | noLineDash: [], 2394 | tempLineDash: [2, 2], 2395 | 2396 | setLineDash: function(dash) { 2397 | 2398 | if (typeof dash === "number") { 2399 | 2400 | this.tempLineDash[0] = dash; 2401 | this.tempLineDash[1] = dash; 2402 | 2403 | dash = this.tempLineDash; 2404 | 2405 | } 2406 | 2407 | this.context.setLineDash(dash ? dash : this.noLineDash); 2408 | 2409 | return this; 2410 | 2411 | }, 2412 | 2413 | measureText: function(text) { 2414 | 2415 | return this.context.measureText(text); 2416 | 2417 | }, 2418 | 2419 | getLineDash: function() { 2420 | 2421 | return this.context.getLineDash(); 2422 | 2423 | }, 2424 | 2425 | createRadialGradient: function(x0, y0, r0, x1, y1, r1) { 2426 | 2427 | return this.context.createRadialGradient(x0, y0, r0, x1, y1, r1); 2428 | 2429 | }, 2430 | 2431 | createLinearGradient: function(x0, y0, x1, y1) { 2432 | 2433 | return this.context.createLinearGradient(x0, y0, x1, y1); 2434 | 2435 | }, 2436 | 2437 | createPattern: function(image, repeat) { 2438 | 2439 | return this.context.createPattern(image, repeat); 2440 | 2441 | }, 2442 | 2443 | getImageData: function(sx, sy, sw, sh) { 2444 | 2445 | return this.context.getImageData(sx, sy, sw, sh); 2446 | 2447 | }, 2448 | 2449 | /* If you think that I am retarded because I use fillRect to set 2450 | pixels - read about premultipled alpha in canvas */ 2451 | 2452 | writeMeta: function(data) { 2453 | 2454 | var json = JSON.stringify(data); 2455 | 2456 | json = encodeURIComponent(json); 2457 | 2458 | var bytes = []; 2459 | 2460 | for (var i = 0; i < json.length; i++) { 2461 | bytes.push(json.charCodeAt(i)); 2462 | // console.log(json[i]) 2463 | } 2464 | 2465 | bytes.push(127); 2466 | 2467 | var x = this.width - 1; 2468 | var y = this.height - 1; 2469 | 2470 | var pixel = []; 2471 | 2472 | while (bytes.length) { 2473 | 2474 | var byte = bytes.shift(); 2475 | 2476 | pixel.unshift(byte * 2); 2477 | // console.log(x + String.fromCharCode(byte), byte); 2478 | 2479 | if (!bytes.length) 2480 | for (var i = 0; i < 3 - pixel.length; i++) pixel.unshift(254); 2481 | 2482 | if (pixel.length === 3) { 2483 | this.fillStyle(cq.color(pixel).toRgb()).fillRect(x, y, 1, 1); 2484 | pixel = []; 2485 | x--; 2486 | 2487 | if (x < 0) { 2488 | y--; 2489 | x = this.width - 1; 2490 | } 2491 | } 2492 | } 2493 | 2494 | return this; 2495 | 2496 | }, 2497 | 2498 | /* setters / getters */ 2499 | 2500 | strokeStyle: function(style) { 2501 | 2502 | if (style == null) { 2503 | 2504 | return this.context.strokeStyle; 2505 | 2506 | } else { 2507 | 2508 | this.context.strokeStyle = style; 2509 | 2510 | return this; 2511 | 2512 | } 2513 | 2514 | }, 2515 | 2516 | fillStyle: function(style) { 2517 | 2518 | if (style == null) { 2519 | 2520 | return this.context.fillStyle; 2521 | 2522 | } else { 2523 | 2524 | this.context.fillStyle = style; 2525 | 2526 | return this; 2527 | 2528 | } 2529 | 2530 | }, 2531 | 2532 | font: function(font) { 2533 | 2534 | if (font == null) { 2535 | 2536 | return this.context.font; 2537 | 2538 | } else { 2539 | 2540 | this.context.font = font; 2541 | 2542 | return this; 2543 | } 2544 | 2545 | }, 2546 | 2547 | filter: function(filter) { 2548 | 2549 | if (filter) { 2550 | 2551 | this.context.filter = filter; 2552 | 2553 | return this; 2554 | 2555 | } else { 2556 | 2557 | return this.context.filter; 2558 | 2559 | } 2560 | 2561 | return this; 2562 | 2563 | }, 2564 | 2565 | readMeta: function() { 2566 | 2567 | var bytes = []; 2568 | 2569 | var x = this.width - 1; 2570 | var y = this.height - 1; 2571 | 2572 | while (true) { 2573 | var pixel = this.getPixel(x, y); 2574 | 2575 | var stop = false; 2576 | 2577 | for (var i = 0; i < 3; i++) { 2578 | 2579 | if (pixel[2 - i] === 254) stop = true; 2580 | 2581 | else bytes.push(pixel[2 - i] / 2 | 0); 2582 | 2583 | } 2584 | 2585 | if (stop) break; 2586 | 2587 | x--; 2588 | 2589 | if (x < 0) { 2590 | y--; 2591 | x = this.width - 1; 2592 | break; 2593 | } 2594 | } 2595 | 2596 | 2597 | var json = ""; 2598 | 2599 | while (bytes.length) { 2600 | json += String.fromCharCode(bytes.shift()); 2601 | } 2602 | 2603 | var data = false; 2604 | 2605 | console.log(json); 2606 | 2607 | try { 2608 | data = JSON.parse(decodeURIComponent(json)); 2609 | } catch (e) { 2610 | 2611 | } 2612 | 2613 | return data; 2614 | 2615 | }, 2616 | 2617 | get width() { 2618 | 2619 | return this.canvas.width; 2620 | 2621 | }, 2622 | 2623 | get height() { 2624 | 2625 | return this.canvas.height; 2626 | 2627 | }, 2628 | 2629 | set width(w) { 2630 | 2631 | this.canvas.width = w; 2632 | this.update(); 2633 | 2634 | return this.canvas.width; 2635 | 2636 | }, 2637 | 2638 | set height(h) { 2639 | 2640 | this.canvas.height = h; 2641 | this.update(); 2642 | 2643 | return this.canvas.height; 2644 | 2645 | } 2646 | 2647 | 2648 | }; 2649 | 2650 | /* extend Layer with drawing context methods */ 2651 | 2652 | var methods = ["arc", "arcTo", "beginPath", "bezierCurveTo", "clip", "closePath", "createLinearGradient", "createRadialGradient", "createPattern", "drawFocusRing", "drawImage", "fill", "fillRect", "fillText", "getImageData", "isPointInPath", "lineTo", "measureText", "moveTo", "putImageData", "quadraticCurveTo", "rect", "restore", "rotate", "scale", "setTransform", "strokeRect", "strokeText", "transform", "translate", "setLineDash"]; 2653 | 2654 | for (var i = 0; i < methods.length; i++) { 2655 | 2656 | var name = methods[i]; 2657 | 2658 | if (cq.Layer.prototype[name]) continue; 2659 | 2660 | cq.Layer.prototype[name] = (function(method) { 2661 | 2662 | return function() { 2663 | 2664 | var args = new Array(arguments.length); 2665 | 2666 | for (var i = 0; i < args.length; ++i) { 2667 | 2668 | args[i] = arguments[i]; 2669 | 2670 | } 2671 | 2672 | cq.fastApply(method, this.context, args); 2673 | 2674 | return this; 2675 | } 2676 | 2677 | })(CanvasRenderingContext2D.prototype[name]); 2678 | 2679 | 2680 | continue; 2681 | 2682 | 2683 | if (!this.debug) { 2684 | // if (!cq.Layer.prototype[name]) cq.Layer.prototype[name] = Function("this.context." + name + ".apply(this.context, arguments); return this;"); 2685 | 2686 | var self = this; 2687 | 2688 | (function(name) { 2689 | 2690 | cq.Layer.prototype[name] = function() { 2691 | // this.context[name].apply(this.context, arguments); 2692 | 2693 | cq.fastApply(this.context[name], this.context, arguments); 2694 | 2695 | return this; 2696 | } 2697 | 2698 | })(name); 2699 | 2700 | } else { 2701 | 2702 | var self = this; 2703 | 2704 | (function(name) { 2705 | 2706 | cq.Layer.prototype[name] = function() { 2707 | try { 2708 | this.context[name].apply(this.context, arguments); 2709 | return this; 2710 | } catch (e) { 2711 | var err = new Error(); 2712 | console.log(err.stack); 2713 | throw (e + err.stack); 2714 | 2715 | console.log(e, name, arguments); 2716 | } 2717 | } 2718 | 2719 | })(name); 2720 | 2721 | } 2722 | 2723 | }; 2724 | 2725 | /* create setters and getters */ 2726 | 2727 | var properties = ["globalAlpha", "globalCompositeOperation", "lineCap", "lineJoin", "lineWidth", "miterLimit", "shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", "textAlign", "textBaseline", "lineDashOffset"]; 2728 | 2729 | for (var i = 0; i < properties.length; i++) { 2730 | 2731 | var name = properties[i]; 2732 | 2733 | if (!cq.Layer.prototype[name]) cq.Layer.prototype[name] = Function("if(arguments.length) { this.context." + name + " = arguments[0]; return this; } else { return this.context." + name + "; }"); 2734 | 2735 | }; 2736 | 2737 | /* color */ 2738 | 2739 | cq.Color = function(data, type) { 2740 | 2741 | if (arguments.length) this.parse(data, type); 2742 | } 2743 | 2744 | cq.Color.prototype = { 2745 | 2746 | toString: function() { 2747 | return this.toRgb(); 2748 | }, 2749 | 2750 | parse: function(args, type) { 2751 | if (args[0] instanceof cq.Color) { 2752 | this[0] = args[0][0]; 2753 | this[1] = args[0][1]; 2754 | this[2] = args[0][2]; 2755 | this[3] = args[0][3]; 2756 | return; 2757 | } 2758 | 2759 | if (typeof args === "string") { 2760 | var match = null; 2761 | 2762 | if (args[0] === "#") { 2763 | var rgb = cq.hexToRgb(args); 2764 | this[0] = rgb[0]; 2765 | this[1] = rgb[1]; 2766 | this[2] = rgb[2]; 2767 | this[3] = 1.0; 2768 | } else if (match = args.match(/rgb\((.*),(.*),(.*)\)/)) { 2769 | this[0] = match[1] | 0; 2770 | this[1] = match[2] | 0; 2771 | this[2] = match[3] | 0; 2772 | this[3] = 1.0; 2773 | } else if (match = args.match(/rgba\((.*),(.*),(.*)\)/)) { 2774 | this[0] = match[1] | 0; 2775 | this[1] = match[2] | 0; 2776 | this[2] = match[3] | 0; 2777 | this[3] = match[4] | 0; 2778 | } else if (match = args.match(/hsl\((.*),(.*),(.*)\)/)) { 2779 | this.fromHsl(match[1], match[2], match[3]); 2780 | } else if (match = args.match(/hsv\((.*),(.*),(.*)\)/)) { 2781 | this.fromHsv(match[1], match[2], match[3]); 2782 | } 2783 | } else { 2784 | switch (type) { 2785 | case "hsl": 2786 | case "hsla": 2787 | 2788 | this.fromHsl(args[0], args[1], args[2], args[3]); 2789 | break; 2790 | 2791 | case "hsv": 2792 | case "hsva": 2793 | 2794 | this.fromHsv(args[0], args[1], args[2], args[3]); 2795 | break; 2796 | 2797 | default: 2798 | this[0] = args[0]; 2799 | this[1] = args[1]; 2800 | this[2] = args[2]; 2801 | this[3] = typeof args[3] === "undefined" ? 1.0 : args[3]; 2802 | break; 2803 | } 2804 | } 2805 | }, 2806 | 2807 | a: function(a) { 2808 | 2809 | if (arguments.length === 1) { 2810 | 2811 | this[3] = a; 2812 | 2813 | } else { 2814 | 2815 | return this[3]; 2816 | 2817 | } 2818 | 2819 | return this; 2820 | 2821 | }, 2822 | 2823 | alpha: function(a) { 2824 | 2825 | if (arguments.length === 1) { 2826 | 2827 | this[3] = a; 2828 | 2829 | } else { 2830 | 2831 | return this[3]; 2832 | 2833 | } 2834 | 2835 | return this; 2836 | 2837 | }, 2838 | 2839 | fromHsl: function() { 2840 | var components = arguments[0] instanceof Array ? arguments[0] : arguments; 2841 | 2842 | var color = cq.hslToRgb(parseFloat(components[0]), parseFloat(components[1]), parseFloat(components[2])); 2843 | 2844 | this[0] = color[0]; 2845 | this[1] = color[1]; 2846 | this[2] = color[2]; 2847 | this[3] = typeof arguments[3] === "undefined" ? 1.0 : arguments[3]; 2848 | }, 2849 | 2850 | fromHsv: function() { 2851 | var components = arguments[0] instanceof Array ? arguments[0] : arguments; 2852 | var color = cq.hsvToRgb(parseFloat(components[0]), parseFloat(components[1]), parseFloat(components[2])); 2853 | 2854 | this[0] = color[0]; 2855 | this[1] = color[1]; 2856 | this[2] = color[2]; 2857 | this[3] = typeof arguments[3] === "undefined" ? 1.0 : arguments[3]; 2858 | }, 2859 | 2860 | toArray: function() { 2861 | return [this[0], this[1], this[2], this[3]]; 2862 | }, 2863 | 2864 | toRgb: function() { 2865 | return "rgb(" + this[0] + ", " + this[1] + ", " + this[2] + ")"; 2866 | }, 2867 | 2868 | toRgba: function() { 2869 | return "rgba(" + this[0] + ", " + this[1] + ", " + this[2] + ", " + this[3] + ")"; 2870 | }, 2871 | 2872 | toHex: function() { 2873 | return cq.rgbToHex(this[0], this[1], this[2]); 2874 | }, 2875 | 2876 | toHsl: function() { 2877 | var c = cq.rgbToHsl(this[0], this[1], this[2]); 2878 | c[3] = this[3]; 2879 | return c; 2880 | }, 2881 | 2882 | toHsv: function() { 2883 | var c = cq.rgbToHsv(this[0], this[1], this[2]); 2884 | c[3] = this[3]; 2885 | return c; 2886 | }, 2887 | 2888 | gradient: function(target, steps) { 2889 | var targetColor = cq.color(target); 2890 | }, 2891 | 2892 | shiftHsl: function() { 2893 | var hsl = this.toHsl(); 2894 | 2895 | if (this[0] !== this[1] || this[1] !== this[2]) { 2896 | var h = arguments[0] === false ? hsl[0] : cq.wrapValue(hsl[0] + arguments[0], 0, 1); 2897 | var s = arguments[1] === false ? hsl[1] : cq.limitValue(hsl[1] + arguments[1], 0, 1); 2898 | } else { 2899 | var h = hsl[0]; 2900 | var s = hsl[1]; 2901 | } 2902 | 2903 | var l = arguments[2] === false ? hsl[2] : cq.limitValue(hsl[2] + arguments[2], 0, 1); 2904 | 2905 | this.fromHsl(h, s, l); 2906 | 2907 | return this; 2908 | }, 2909 | 2910 | setHsl: function() { 2911 | var hsl = this.toHsl(); 2912 | 2913 | var h = arguments[0] === false ? hsl[0] : cq.limitValue(arguments[0], 0, 1); 2914 | var s = arguments[1] === false ? hsl[1] : cq.limitValue(arguments[1], 0, 1); 2915 | var l = arguments[2] === false ? hsl[2] : cq.limitValue(arguments[2], 0, 1); 2916 | 2917 | this.fromHsl(h, s, l); 2918 | 2919 | return this; 2920 | }, 2921 | 2922 | mix: function(color, amount) { 2923 | 2924 | color = cq.color(color); 2925 | 2926 | for (var i = 0; i < 4; i++) { 2927 | 2928 | this[i] = cq.mix(this[i], color[i], amount); 2929 | 2930 | } 2931 | 2932 | return this; 2933 | 2934 | } 2935 | 2936 | }; 2937 | 2938 | window["cq"] = window["CanvasQuery"] = cq; 2939 | 2940 | return cq; 2941 | 2942 | })(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CanvasQuery", 3 | "version": "1.0.0", 4 | "description": "Canvas Query is a wrapper library for HTML5 Canvas element which allows it to be used with jQuery like syntax.", 5 | "keywords": [ "html5", "client-side", "canvas", "development" ], 6 | "main": "canvasquery.js", 7 | "homepage": "http://canvasquery.com/", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/rezoner/CanvasQuery.git" 11 | }, 12 | "bugs": { 13 | "url" : "https://github.com/rezoner/CanvasQuery/issues" 14 | }, 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /src/framework.js: -------------------------------------------------------------------------------- 1 | /* Micro framework */ 2 | 3 | cq.images = {}; 4 | cq.atlases = {}; 5 | cq.loaderscount = 0; 6 | cq.loadercallback = function() { 7 | 8 | cq.loaderscount--; 9 | 10 | }; 11 | 12 | cq.loadImages = function(keys) { 13 | 14 | var promises = []; 15 | 16 | for (var key in keys) { 17 | 18 | cq.loaderscount++; 19 | 20 | var path = keys[key]; 21 | 22 | var image = new orgImage(); 23 | 24 | cq.images[key] = image; 25 | cq.loaderscount++; 26 | 27 | var promise = new Promise(function(resolve, reject) { 28 | 29 | image.addEventListener("load", function() { 30 | 31 | cq.loadercallback(); 32 | 33 | resolve(); 34 | 35 | }); 36 | 37 | image.addEventListener("error", function() { 38 | 39 | throw ("unable to load " + this.src); 40 | 41 | }); 42 | 43 | }); 44 | 45 | image.src = path; 46 | 47 | } 48 | 49 | return Promise.all(promises); 50 | 51 | }; 52 | 53 | cq.loadAtlases = function() { 54 | 55 | }; 56 | 57 | /* WIP */ 58 | 59 | cq.run = function(callback) { 60 | 61 | var lasTick = Date.now(); 62 | 63 | var frame = function() { 64 | 65 | requestAnimationFrame(frame); 66 | 67 | var dt = Date.now() - lastTick; 68 | lastTick = Date.now(); 69 | 70 | if (cq.loaderscount === 0) callback(dt); 71 | 72 | } 73 | 74 | requestAnimationFrame(frame); 75 | 76 | }; 77 | 78 | /* WIP */ 79 | 80 | cq.viewport = function() { 81 | 82 | if (!cq.layer) { 83 | 84 | cq.layer = cq(); 85 | cq.layer.appendTo(document.body); 86 | 87 | } 88 | 89 | }; 90 | 91 | /* WIP */ 92 | cq.mouse = function(callback) { 93 | 94 | document.addEventListener('mousedown', function(e) { 95 | 96 | console.log(e); 97 | 98 | }); 99 | 100 | } -------------------------------------------------------------------------------- /src/text/textBoundaries.js: -------------------------------------------------------------------------------- 1 | CanvasQuery.Layer.prototype.textBoundaries = function(text, maxWidth) { 2 | 3 | if (maxWidth < 0) maxWidth = 0; 4 | 5 | var words = text.split(" "); 6 | 7 | var h = this.fontHeight(); 8 | 9 | var ox = 0; 10 | var oy = 0; 11 | 12 | var spaceWidth = this.context.measureText(" ").width; 13 | 14 | var line = 0; 15 | var lines = [""]; 16 | 17 | var width = 0; 18 | 19 | for (var i = 0; i < words.length; i++) { 20 | 21 | var word = words[i]; 22 | var wordWidth = Math.ceil(this.context.measureText(word).width); 23 | 24 | if (maxWidth && (wordWidth > maxWidth)) { 25 | 26 | if (word.length <= 5) continue; 27 | 28 | var split = word.length / 2 | 0; 29 | 30 | words.splice(i, 1); 31 | words.splice(i, 0, "-" + word.substr(split)); 32 | words.splice(i, 0, word.substr(0, split) + "-"); 33 | 34 | i--; 35 | 36 | continue; 37 | } 38 | 39 | if (((ox + wordWidth > maxWidth) && maxWidth) || words[i] === "\n") { 40 | 41 | if (ox > width) width = ox; 42 | 43 | lines[++line] = ""; 44 | 45 | ox = 0; 46 | 47 | } 48 | 49 | if (words[i] !== "\n") { 50 | 51 | lines[line] += word; 52 | 53 | ox += wordWidth + spaceWidth; 54 | 55 | } 56 | 57 | } 58 | 59 | if (maxWidth) { 60 | 61 | var width = maxWidth; 62 | 63 | } else { 64 | 65 | if (!width) { 66 | 67 | width = this.context.measureText(text).width; 68 | 69 | } 70 | 71 | } 72 | 73 | return { 74 | height: lines.length * h, 75 | width: Math.ceil(width), 76 | lines: lines.length, 77 | fontHeight: h 78 | } 79 | 80 | }; -------------------------------------------------------------------------------- /src/text/wrappedText.js: -------------------------------------------------------------------------------- 1 | CanvasQuery.Layer.prototype.wrappedText = function(text, x, y, maxWidth, lineHeight) { 2 | 3 | if (maxWidth < 0) maxWidth = 0; 4 | 5 | var words = text.split(" "); 6 | 7 | var lineHeight = lineHeight || this.fontHeight(); 8 | 9 | var ox = 0; 10 | var oy = 0; 11 | 12 | var textAlign = this.context.textAlign; 13 | var textBaseline = this.context.textBaseline; 14 | 15 | this.textBaseline("top"); 16 | 17 | var spaceWidth = this.context.measureText(" ").width | 0; 18 | 19 | if (maxWidth) { 20 | 21 | var line = 0; 22 | var lines = [""]; 23 | var linesWidth = [0]; 24 | 25 | for (var i = 0; i < words.length; i++) { 26 | 27 | var word = words[i]; 28 | 29 | var wordWidth = Math.ceil(this.context.measureText(word).width); 30 | 31 | 32 | if (wordWidth > maxWidth) { 33 | 34 | /* 4 is still risky, it's valid as long as `-` is the delimiter */ 35 | 36 | if (word.length <= 5) return; 37 | 38 | var split = word.length / 2 | 0; 39 | 40 | words.splice(i, 1); 41 | words.splice(i, 0, "-" + word.substr(split)); 42 | words.splice(i, 0, word.substr(0, split) + "-"); 43 | 44 | i--; 45 | 46 | continue; 47 | } 48 | 49 | if (ox + wordWidth > maxWidth || words[i] === "\n") { 50 | 51 | lines[line] = lines[line].substr(0, lines[line].length - 1); 52 | linesWidth[line] -= spaceWidth; 53 | 54 | lines[++line] = ""; 55 | linesWidth[line] = 0; 56 | ox = 0; 57 | } 58 | 59 | if (words[i] !== "\n") { 60 | 61 | lines[line] += word + " "; 62 | 63 | ox += wordWidth + spaceWidth; 64 | 65 | linesWidth[line] += wordWidth + spaceWidth; 66 | 67 | } 68 | 69 | } 70 | 71 | if (words[i] !== "\n") { 72 | lines[line] = lines[line].substr(0, lines[line].length - 1); 73 | linesWidth[line] -= spaceWidth; 74 | } 75 | 76 | 77 | } else { 78 | 79 | var lines = [text]; 80 | var linesWidth = [this.context.measureText(text).width]; 81 | 82 | } 83 | 84 | for (var i = 0; i < lines.length; i++) { 85 | 86 | var oy = y + i * lineHeight | 0; 87 | 88 | var text = lines[i]; 89 | var width = linesWidth[i]; 90 | 91 | this.textAlign("left"); 92 | 93 | if (textAlign === "left" || textAlign === "start") 94 | this.fillText(text, x, oy); 95 | else if (textAlign === "center") 96 | this.fillText(text, x + maxWidth * 0.5 - width * 0.5 | 0, oy); 97 | else 98 | this.fillText(text, x + maxWidth - width, oy); 99 | 100 | } 101 | 102 | this.textAlign(textAlign); 103 | this.textBaseline(textBaseline); 104 | 105 | return this; 106 | 107 | }; --------------------------------------------------------------------------------