diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..73e1bc3 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +canvas_physics-master \ No newline at end of file diff --git a/.idea/canvas_physics-master.iml b/.idea/canvas_physics-master.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/canvas_physics-master.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..8c20f1b --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..72abef0 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e388e70 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..514a49b --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + CSS + + + Probable bugsCSS + + + RELAX NG + + + XPath + + + XSLT + + + + + CoffeeScript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $PROJECT_DIR$ + true + + bdd + + DIRECTORY + + false + + + + + + + + + + + 1457490972414 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Template.html b/Template.html new file mode 100644 index 0000000..9ae1e89 --- /dev/null +++ b/Template.html @@ -0,0 +1,26 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c01.html b/c01.html new file mode 100644 index 0000000..c11136d --- /dev/null +++ b/c01.html @@ -0,0 +1,101 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c02.html b/c02.html new file mode 100644 index 0000000..f71c6da --- /dev/null +++ b/c02.html @@ -0,0 +1,106 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c03.html b/c03.html new file mode 100644 index 0000000..6667bac --- /dev/null +++ b/c03.html @@ -0,0 +1,117 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c04.html b/c04.html new file mode 100644 index 0000000..708fefc --- /dev/null +++ b/c04.html @@ -0,0 +1,97 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c05.html b/c05.html new file mode 100644 index 0000000..37dfcdc --- /dev/null +++ b/c05.html @@ -0,0 +1,139 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c06.html b/c06.html new file mode 100644 index 0000000..537a8b1 --- /dev/null +++ b/c06.html @@ -0,0 +1,104 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c07.html b/c07.html new file mode 100644 index 0000000..d93fefd --- /dev/null +++ b/c07.html @@ -0,0 +1,143 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c08.html b/c08.html new file mode 100644 index 0000000..ce7bd33 --- /dev/null +++ b/c08.html @@ -0,0 +1,175 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c09.html b/c09.html new file mode 100644 index 0000000..df505b7 --- /dev/null +++ b/c09.html @@ -0,0 +1,241 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/c10.html b/c10.html new file mode 100644 index 0000000..b913cb6 --- /dev/null +++ b/c10.html @@ -0,0 +1,276 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/canvas/LICENSE.txt b/canvas/LICENSE.txt new file mode 100644 index 0000000..5c8a504 --- /dev/null +++ b/canvas/LICENSE.txt @@ -0,0 +1,30 @@ +Copyright (C) 2012 David Geary. + +This code is from the book Core HTML5 Canvas, published by Prentice-Hall in 2012. + +License: + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +The Software may not be used to create training material of any sort, +including courses, books, instructional videos, presentations, etc. +without the express written consent of David Geary. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + diff --git a/canvas/README.txt b/canvas/README.txt new file mode 100644 index 0000000..82cb38d --- /dev/null +++ b/canvas/README.txt @@ -0,0 +1,107 @@ +This is the source code from the book Core HTML5 Canvas, published by +Prentice-Hall in May 2012. See LICENSE.txt for the minimal restrictions on its +use. (Yes, you can use any of the code in commercial products as long as you're +not using it for education material such as books, videos, presentations, etc.) + +YOU MUST CONFIGURE YOUR BROWSER TO RUN SOME EXAMPLES LOCALLY: + + The following examples perform image manipulation and will therefore result + in security exceptions when you run them on your local filesystem. See the + Security section of the Images chapter in Core HTML5 Canvas for information + on bypassing the browser's security so you can run those examples: + + example-4.9 + example-4.13 + example-4.14 + example-4.15 + example-4.16 + example-4.18 + example-4.20 + + +Overview........................................................................ + +You can access all of the examples by loading index.html in an HTML5-capable +browser. + +The examples are grouped by chapter, and mostly by example number. For instance, +here are the directories that you'll find in the ch08 directory: + + example-8.1 + example-8.10 + example-8.19 + example-8.2 + example-8.20 + example-8.8 + + section-8.1.1 + section-8.3 + section-8.4.1.6 + +The example numbers correspond to listings in the book. Many listings in the +book are partial listings, so example numbers will often jump around, which +is the case for chapter 8 above, which has several partial listings between +example-8.2 and example-8.8. + +Some examples do not have listings. I discuss the examples in the book, and +perhaps show a little of their code, but not enough for a full listing, so +you can find those listings by their sections instead, as is the case for +the last three examples in chapter 8 listed above. + +Some examples that you have downloaded are slightly different from the +listings in the book; for example, I have put all images shared among examples +in shared/images, and I've modified the examples to use those images to save +space in the zip file. I've also added the security fix for Firefox discussed in +section 4.8 to several of the examples so the examples run without intervention. + + +Browser Anomolies............................................................... + +The latest browser versions that I've tested this code with: + + Firefox 12.0 + Chrome 20.0 + Safari 5.0.5 + Opera 11.6.2 + IE9/10 + +Known Bugs: + +1. Opera and IE9/10 do not implement arcTo() correctly (see bit.ly/HVvnT8), which + affects the following examples: + + example-2.20 + example-4.22 + section-6.3.2 + example-7.1 + section-8.1.1 + example-10.4 (Opera, in addition to incorrectly drawing the arcs, is incredibly slow here) + example-10.7 + +2. Opera does not fill text correctly, affecting the following examples: example-3.1, example-3.5. +3. Firefox does not fill text with a gradient correctly in example-3.2. +4. Paragraphs do not indent correctly in Firefox and Opera in example-3.18. +5. Opera does not redraw the image as you drag the slider in example-4.6. +6. Firefox and Opera draw watermark text with the wrong colors in example-4.8. +7. Firefox and Chrome draw the bomb at the wrong scale. + + +Performance..................................................................... + +HTML5 Canvas performance varies significantly between devices and operating +systems, but in general it's very good. When the book was released, performance +was still pitiful on Android, but pretty kickass on iOS5. See http://bit.ly/HsyFOG +on an iPad for some examples. + +When you run some of the more intense examples in the book, especially games +such as the bucket game and pinball, those examples will run slowly if you have +a million tabs and browser windows open, which is always the case for me. +So when you run those examples, close your other windows and tabs. + + +Thank You....................................................................... + +Thank you for downloading the code. I hope you find it, and the book, useful. + + +David Geary diff --git a/canvas/ch01/example-1.1/example.html b/canvas/ch01/example-1.1/example.html new file mode 100644 index 0000000..ca77c8d --- /dev/null +++ b/canvas/ch01/example-1.1/example.html @@ -0,0 +1,58 @@ + + + + + + Canvas element size: 600 x 300, Canvas drawing surface size: 600 x 300 + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch01/example-1.1/example.js b/canvas/ch01/example-1.1/example.js new file mode 100644 index 0000000..3dffe5e --- /dev/null +++ b/canvas/ch01/example-1.1/example.js @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'); + +context.font = '38pt Arial'; +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'blue'; + +context.fillText("Hello Canvas", canvas.width/2 - 150, + canvas.height/2 + 15); + +context.strokeText("Hello Canvas", canvas.width/2 - 150, + canvas.height/2 + 15 ); diff --git a/canvas/ch01/example-1.11/example.html b/canvas/ch01/example-1.11/example.html new file mode 100644 index 0000000..a65f8eb --- /dev/null +++ b/canvas/ch01/example-1.11/example.html @@ -0,0 +1,72 @@ + + + + + Clock + + + + + +
+ +
+ + + + + Canvas not supported + + + + + diff --git a/canvas/ch01/example-1.11/example.js b/canvas/ch01/example-1.11/example.js new file mode 100644 index 0000000..3376161 --- /dev/null +++ b/canvas/ch01/example-1.11/example.js @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + snapshotButton = document.getElementById('snapshotButton'), + snapshotImageElement = document.getElementById('snapshotImageElement'), + FONT_HEIGHT = 15, + MARGIN = 35, + HAND_TRUNCATION = canvas.width/25, + HOUR_HAND_TRUNCATION = canvas.width/10, + NUMERAL_SPACING = 20, + RADIUS = canvas.width/2 - MARGIN, + HAND_RADIUS = RADIUS + NUMERAL_SPACING, + loop; + +// Functions..................................................... + +function drawCircle() { + context.beginPath(); + context.arc(canvas.width/2, canvas.height/2, RADIUS, 0, Math.PI*2, true); + context.stroke(); +} + +function drawNumerals() { + var numerals = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], + angle = 0, + numeralWidth = 0; + + numerals.forEach(function(numeral) { + angle = Math.PI/6 * (numeral-3); + numeralWidth = context.measureText(numeral).width; + context.fillText(numeral, + canvas.width/2 + Math.cos(angle)*(HAND_RADIUS) - numeralWidth/2, + canvas.height/2 + Math.sin(angle)*(HAND_RADIUS) + FONT_HEIGHT/3); + }); +} + +function drawCenter() { + context.beginPath(); + context.arc(canvas.width/2, canvas.height/2, 5, 0, Math.PI*2, true); + context.fill(); +} + +function drawHand(loc, isHour) { + var angle = (Math.PI*2) * (loc/60) - Math.PI/2, + handRadius = isHour ? RADIUS-HAND_TRUNCATION-HOUR_HAND_TRUNCATION + : RADIUS-HAND_TRUNCATION; + + context.moveTo(canvas.width/2, canvas.height/2); + context.lineTo(canvas.width/2 + Math.cos(angle)*handRadius, + canvas.height/2 + Math.sin(angle)*handRadius); + context.stroke(); +} + +function drawHands() { + var date = new Date, + hour = date.getHours(); + hour = hour > 12 ? hour - 12 : hour; + drawHand(hour*5 + (date.getMinutes()/60)*5, true, 0.5); + drawHand(date.getMinutes(), false, 0.5); + drawHand(date.getSeconds(), false, 0.2); +} + +function drawClock() { + context.clearRect(0,0,canvas.width,canvas.height); + + context.save(); + + context.fillStyle = 'rgba(255,255,255,0.8)'; + context.fillRect(0, 0, canvas.width, canvas.height); + + drawCircle(); + drawCenter(); + drawHands(); + + context.restore(); + + drawNumerals(); +} + +// Event handlers................................................ + +snapshotButton.onclick = function (e) { + var dataUrl; + + if (snapshotButton.value === 'Take snapshot') { + dataUrl = canvas.toDataURL(); + clearInterval(loop); + snapshotImageElement.src = dataUrl; + snapshotImageElement.style.display = 'inline'; + canvas.style.display = 'none'; + snapshotButton.value = 'Return to Canvas'; + } + else { + snapshotButton.value = 'Take snapshot'; + canvas.style.display = 'inline'; + snapshotImageElement.style.display = 'none'; + loop = setInterval(drawClock, 1000); + } +}; + +// Initialization................................................ + +context.font = FONT_HEIGHT + 'px Arial'; +loop = setInterval(drawClock, 1000); diff --git a/canvas/ch01/example-1.13/example.html b/canvas/ch01/example-1.13/example.html new file mode 100644 index 0000000..96a3ba8 --- /dev/null +++ b/canvas/ch01/example-1.13/example.html @@ -0,0 +1,63 @@ + + + + + Image Clock + + + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch01/example-1.13/example.js b/canvas/ch01/example-1.13/example.js new file mode 100644 index 0000000..142e5f9 --- /dev/null +++ b/canvas/ch01/example-1.13/example.js @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + snapshotButton = document.getElementById('snapshotButton'), + snapshotImageElement = document.getElementById('snapshotImageElement'), + FONT_HEIGHT = 15, + MARGIN = 35, + HAND_TRUNCATION = canvas.width/25, + HOUR_HAND_TRUNCATION = canvas.width/10, + NUMERAL_SPACING = 20, + RADIUS = canvas.width/2 - MARGIN, + HAND_RADIUS = RADIUS + NUMERAL_SPACING, + loop; + +// Functions..................................................... + +function drawCircle() { + context.beginPath(); + context.arc(canvas.width/2, canvas.height/2, RADIUS, 0, Math.PI*2, true); + context.stroke(); +} + +function drawNumerals() { + var numerals = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], + angle = 0, + numeralWidth = 0; + + numerals.forEach(function(numeral) { + angle = Math.PI/6 * (numeral-3); + numeralWidth = context.measureText(numeral).width; + context.fillText(numeral, + canvas.width/2 + Math.cos(angle)*(HAND_RADIUS) - numeralWidth/2, + canvas.height/2 + Math.sin(angle)*(HAND_RADIUS) + FONT_HEIGHT/3); + }); +} + +function drawCenter() { + context.beginPath(); + context.arc(canvas.width/2, canvas.height/2, 5, 0, Math.PI*2, true); + context.fill(); +} + +function drawHand(loc, isHour) { + var angle = (Math.PI*2) * (loc/60) - Math.PI/2, + handRadius = isHour ? RADIUS-HAND_TRUNCATION-HOUR_HAND_TRUNCATION + : RADIUS-HAND_TRUNCATION; + + context.moveTo(canvas.width/2, canvas.height/2); + context.lineTo(canvas.width/2 + Math.cos(angle)*handRadius, + canvas.height/2 + Math.sin(angle)*handRadius); + context.stroke(); +} + +function drawHands() { + var date = new Date, + hour = date.getHours(); + hour = hour > 12 ? hour - 12 : hour; + drawHand(hour*5 + (date.getMinutes()/60)*5, true, 0.5); + drawHand(date.getMinutes(), false, 0.5); + drawHand(date.getSeconds(), false, 0.2); +} + +function updateClockImage() { + dataUrl = canvas.toDataURL(); + snapshotImageElement.src = dataUrl; +} + +function drawClock() { + context.clearRect(0,0,canvas.width,canvas.height); + + context.save(); + + context.fillStyle = 'rgba(255,255,255,0.8)'; + context.fillRect(0, 0, canvas.width, canvas.height); + + drawCircle(); + drawCenter(); + drawHands(); + + context.restore(); + + drawNumerals(); + + updateClockImage(); +} + +// Initialization................................................ + +context.font = FONT_HEIGHT + 'px Arial'; +loop = setInterval(drawClock, 1000); diff --git a/canvas/ch01/example-1.3/example.html b/canvas/ch01/example-1.3/example.html new file mode 100644 index 0000000..c0439f7 --- /dev/null +++ b/canvas/ch01/example-1.3/example.html @@ -0,0 +1,59 @@ + + + + + Canvas element size: 600 x 300, Canvas drawing surface size: 300 x 150 + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch01/example-1.3/example.js b/canvas/ch01/example-1.3/example.js new file mode 100644 index 0000000..737af0b --- /dev/null +++ b/canvas/ch01/example-1.3/example.js @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'); + +context.font = '38pt Arial'; +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'blue'; + +context.fillText("Hello Canvas", canvas.width/2 - 150, + canvas.height/2 + 15); + +context.strokeText("Hello Canvas", canvas.width/2 - 150, + canvas.height/2 + 15); diff --git a/canvas/ch01/example-1.4/example.html b/canvas/ch01/example-1.4/example.html new file mode 100644 index 0000000..d84059d --- /dev/null +++ b/canvas/ch01/example-1.4/example.html @@ -0,0 +1,60 @@ + + + + + + Clock + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch01/example-1.4/example.js b/canvas/ch01/example-1.4/example.js new file mode 100644 index 0000000..7ca9523 --- /dev/null +++ b/canvas/ch01/example-1.4/example.js @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + FONT_HEIGHT = 15, + MARGIN = 35, + HAND_TRUNCATION = canvas.width/25, + HOUR_HAND_TRUNCATION = canvas.width/10, + NUMERAL_SPACING = 20, + RADIUS = canvas.width/2 - MARGIN, + HAND_RADIUS = RADIUS + NUMERAL_SPACING; + +// Functions..................................................... + +function drawCircle() { + context.beginPath(); + context.arc(canvas.width/2, canvas.height/2, + RADIUS, 0, Math.PI*2, true); + context.stroke(); +} + +function drawNumerals() { + var numerals = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ], + angle = 0, + numeralWidth = 0; + + numerals.forEach(function(numeral) { + angle = Math.PI/6 * (numeral-3); + numeralWidth = context.measureText(numeral).width; + context.fillText(numeral, + canvas.width/2 + Math.cos(angle)*(HAND_RADIUS) - numeralWidth/2, + canvas.height/2 + Math.sin(angle)*(HAND_RADIUS) + FONT_HEIGHT/3); + }); +} + +function drawCenter() { + context.beginPath(); + context.arc(canvas.width/2, canvas.height/2, 5, 0, Math.PI*2, true); + context.fill(); +} + +function drawHand(loc, isHour) { + var angle = (Math.PI*2) * (loc/60) - Math.PI/2, + handRadius = isHour ? RADIUS - HAND_TRUNCATION-HOUR_HAND_TRUNCATION + : RADIUS - HAND_TRUNCATION; + + context.moveTo(canvas.width/2, canvas.height/2); + context.lineTo(canvas.width/2 + Math.cos(angle)*handRadius, + canvas.height/2 + Math.sin(angle)*handRadius); + context.stroke(); +} + +function drawHands() { + var date = new Date, + hour = date.getHours(); + hour = hour > 12 ? hour - 12 : hour; + drawHand(hour*5 + (date.getMinutes()/60)*5, true, 0.5); + drawHand(date.getMinutes(), false, 0.5); + drawHand(date.getSeconds(), false, 0.2); +} + +function drawClock() { + context.clearRect(0,0,canvas.width,canvas.height); + + drawCircle(); + drawCenter(); + drawHands(); + drawNumerals(); +} + +// Initialization................................................ + +context.font = FONT_HEIGHT + 'px Arial'; +loop = setInterval(drawClock, 1000); diff --git a/canvas/ch01/example-1.5/example.html b/canvas/ch01/example-1.5/example.html new file mode 100644 index 0000000..ce29119 --- /dev/null +++ b/canvas/ch01/example-1.5/example.html @@ -0,0 +1,69 @@ + + + + + Sprite sheets + + + + + +
+ + + Canvas not supported + + + + + diff --git a/canvas/ch01/example-1.5/example.js b/canvas/ch01/example-1.5/example.js new file mode 100644 index 0000000..3e4cf74 --- /dev/null +++ b/canvas/ch01/example-1.5/example.js @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.querySelector('#canvas'), + readout = document.querySelector('#readout'), + context = canvas.getContext('2d'), + spritesheet = new Image(); + +// Functions..................................................... + +function windowToCanvas(canvas, x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +function drawBackground() { + var VERTICAL_LINE_SPACING = 12, + i = context.canvas.height; + + context.clearRect(0,0,canvas.width,canvas.height); + context.strokeStyle = 'lightgray'; + context.lineWidth = 0.5; + + while(i > VERTICAL_LINE_SPACING*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= VERTICAL_LINE_SPACING; + } +} + +function drawSpritesheet() { + context.drawImage(spritesheet, 0, 0); +} + +function drawGuidelines(x, y) { + context.strokeStyle = 'rgba(0,0,230,0.8)'; + context.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); +} + +function updateReadout(x, y) { + readout.innerHTML = '(' + x.toFixed(0) + ', ' + y.toFixed(0) + ')'; +} + +function drawHorizontalLine (y) { + context.beginPath(); + context.moveTo(0,y + 0.5); + context.lineTo(context.canvas.width, y + 0.5); + context.stroke(); +} + +function drawVerticalLine (x) { + context.beginPath(); + context.moveTo(x + 0.5, 0); + context.lineTo(x + 0.5, context.canvas.height); + context.stroke(); +} + +// Event handlers..................................................... + +canvas.onmousemove = function (e) { + var loc = windowToCanvas(canvas, e.clientX, e.clientY); + + drawBackground(); + drawSpritesheet(); + drawGuidelines(loc.x, loc.y); + updateReadout(loc.x, loc.y); +}; + +// Initialization..................................................... + +spritesheet.src = '../../shared/images/running-sprite-sheet.png'; +spritesheet.onload = function(e) { + drawSpritesheet(); +}; + +drawBackground(); diff --git a/canvas/ch01/example-1.8/example.html b/canvas/ch01/example-1.8/example.html new file mode 100644 index 0000000..2212e3b --- /dev/null +++ b/canvas/ch01/example-1.8/example.html @@ -0,0 +1,108 @@ + + + + + + Bouncing Balls + + + + + +
+

Bouncing Balls

+ +

One hundred balls bouncing

+ + Start +
+ + + Canvas not supported + + + + + diff --git a/canvas/ch01/example-1.8/example.js b/canvas/ch01/example-1.8/example.js new file mode 100644 index 0000000..a9e1d49 --- /dev/null +++ b/canvas/ch01/example-1.8/example.js @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'), + startButton = document.getElementById('startButton'), + glasspane = document.getElementById('glasspane'), + paused = true, + circles = []; + +drawGrid(context, 'lightgray', 10, 10); + +context.lineWidth = 0.5; +context.font = '32pt Arial'; + +for (var i=0; i < 100; ++i) { + circles[i] = { + x: 100, + y: 100, + velocityX: 3*Math.random(), + velocityY: 3*Math.random(), + radius: 50*Math.random(), + color: 'rgba(' + (Math.random()*255).toFixed(0) + ', ' + + (Math.random()*255).toFixed(0) + ', ' + + (Math.random()*255).toFixed(0) + ', 1.0)' }; +} + +startButton.onclick = function(e) { + e.preventDefault(); + e.stopPropagation(); + paused = ! paused; + startButton.innerText = paused ? 'Start' : 'Stop'; +}; + +glasspane.onmousedown = function(e) { + e.preventDefault(); + e.stopPropagation(); +} + +context.canvas.onmousedown = function(e) { + e.preventDefault(); + e.stopPropagation(); +}; + +setInterval(function() { + if (!paused) { + context.clearRect(0, 0, context.canvas.width, context.canvas.height); + drawGrid(context, 'lightgray', 10, 10); + + circles.forEach(function(circle) { + context.beginPath(); + context.arc(circle.x, circle.y, circle.radius, 0, Math.PI*2, false); + context.fillStyle = circle.color; + context.fill(); + adjustPosition(circle); + }); + } +}, 1000 / 60); + +function adjustPosition(circle) { + if (circle.x + circle.velocityX + circle.radius > context.canvas.width || + circle.x + circle.velocityX - circle.radius < 0) + circle.velocityX = -circle.velocityX; + + if (circle.y + circle.velocityY + circle.radius > context.canvas.height || + circle.y + circle.velocityY - circle.radius < 0) + circle.velocityY= -circle.velocityY; + + circle.x += circle.velocityX; + circle.y += circle.velocityY; +} + +function drawGrid(context, color, stepx, stepy) { + context.strokeStyle = color; + context.lineWidth = 0.5; + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } +} diff --git a/canvas/ch01/example-1.9/example.html b/canvas/ch01/example-1.9/example.html new file mode 100644 index 0000000..0923296 --- /dev/null +++ b/canvas/ch01/example-1.9/example.html @@ -0,0 +1,78 @@ + + + + + + Rubberbands with layered elements + + + + + +
+ +
+ +
+ + + Canvas not supported + + + + + diff --git a/canvas/ch01/example-1.9/example.js b/canvas/ch01/example-1.9/example.js new file mode 100644 index 0000000..622a7ed --- /dev/null +++ b/canvas/ch01/example-1.9/example.js @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + rubberbandDiv = document.getElementById('rubberbandDiv'), + resetButton = document.getElementById('resetButton'), + image = new Image(), + mousedown = {}, + rubberbandRectangle = {}, + dragging = false; + +// Functions..................................................... + +function rubberbandStart(x, y) { + mousedown.x = x; + mousedown.y = y; + + rubberbandRectangle.left = mousedown.x; + rubberbandRectangle.top = mousedown.y; + + moveRubberbandDiv(); + showRubberbandDiv(); + + dragging = true; +} + +function rubberbandStretch(x, y) { + rubberbandRectangle.left = x < mousedown.x ? x : mousedown.x; + rubberbandRectangle.top = y < mousedown.y ? y : mousedown.y; + + rubberbandRectangle.width = Math.abs(x - mousedown.x), + rubberbandRectangle.height = Math.abs(y - mousedown.y); + + moveRubberbandDiv(); + resizeRubberbandDiv(); +}; + +function rubberbandEnd() { + var bbox = canvas.getBoundingClientRect(); + + try { + context.drawImage(canvas, + rubberbandRectangle.left - bbox.left, + rubberbandRectangle.top - bbox.top, + rubberbandRectangle.width, + rubberbandRectangle.height, + 0, 0, canvas.width, canvas.height); + } + catch (e) { + // suppress error message when mouse is released + // outside the canvas + } + + resetRubberbandRectangle(); + + rubberbandDiv.style.width = 0; + rubberbandDiv.style.height = 0; + + hideRubberbandDiv(); + + dragging = false; +} + +function moveRubberbandDiv() { + rubberbandDiv.style.top = rubberbandRectangle.top + 'px'; + rubberbandDiv.style.left = rubberbandRectangle.left + 'px'; +} + +function resizeRubberbandDiv() { + rubberbandDiv.style.width = rubberbandRectangle.width + 'px'; + rubberbandDiv.style.height = rubberbandRectangle.height + 'px'; +} + +function showRubberbandDiv() { + rubberbandDiv.style.display = 'inline'; +} + +function hideRubberbandDiv() { + rubberbandDiv.style.display = 'none'; +} + +function resetRubberbandRectangle() { + rubberbandRectangle = { top: 0, left: 0, width: 0, height: 0 }; +} + +// Event handlers............................................... + +canvas.onmousedown = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY; + + e.preventDefault(); + rubberbandStart(x, y); +}; + +window.onmousemove = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY; + + e.preventDefault(); + if (dragging) { + rubberbandStretch(x, y); + } +} + +window.onmouseup = function (e) { + e.preventDefault(); + rubberbandEnd(); +} + +// Event handlers.............................................. + +image.onload = function () { + context.drawImage(image, 0, 0, canvas.width, canvas.height); +}; + +resetButton.onclick = function(e) { + context.clearRect(0, 0, context.canvas.width, + context.canvas.height); + context.drawImage(image, 0, 0, canvas.width, canvas.height); +}; + +// Initialization.............................................. + +image.src = '../../shared/images/arch.png'; diff --git a/canvas/ch02/example-2.1/example.html b/canvas/ch02/example-2.1/example.html new file mode 100644 index 0000000..74f1edb --- /dev/null +++ b/canvas/ch02/example-2.1/example.html @@ -0,0 +1,55 @@ + + + + + Rectangles + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.1/example.js b/canvas/ch02/example-2.1/example.js new file mode 100644 index 0000000..9e974e1 --- /dev/null +++ b/canvas/ch02/example-2.1/example.js @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'); + +context.lineWidth = 30; + +context.font = '24px Helvetica'; +context.fillText('Click anywhere to erase', 175, 40); + +context.strokeRect(75, 100, 200, 200); +context.fillRect(325, 100, 200, 200); + +context.canvas.onmousedown = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); +}; diff --git a/canvas/ch02/example-2.10/example.html b/canvas/ch02/example-2.10/example.html new file mode 100644 index 0000000..b8123f9 --- /dev/null +++ b/canvas/ch02/example-2.10/example.html @@ -0,0 +1,73 @@ + + + + + + Cutouts with the nonzero winding rule + + + + + +
+ Same direction
+ Annotations
+
+ + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.10/example.js b/canvas/ch02/example-2.10/example.js new file mode 100644 index 0000000..f23c8ba --- /dev/null +++ b/canvas/ch02/example-2.10/example.js @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'), + directionCheckbox = document.getElementById('directionCheckbox'), + annotationCheckbox = document.getElementById('annotationCheckbox'), + CLOCKWISE = 1, + COUNTER_CLOCKWISE = 2; + +// Functions..................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function drawText() { + context.save(); + context.font = '18px Arial'; + context.fillStyle = 'rgb(0, 0, 200)'; + context.fillText('Two arcs, one path', 10, 30); + + context.font = '16px Lucida Sans'; + context.fillStyle = 'navy'; + context.fillText('context.arc(300, 200, 150, 0, Math.PI*2, false)', 10, 360); + context.fillText('context.arc(300, 200, 100, 0, Math.PI*2, !sameDirection)', 10, 380); + context.restore(); +} + +function drawArcAnnotations(sameDirection) { + context.save(); + context.font = '16px Lucida Sans'; + context.fillStyle = 'blue'; + context.fillText('CW', 345, 145); + context.fillText(sameDirection ? 'CW' : 'CCW', 425, 75); + context.restore(); +} + +function drawOuterCircleAnnotations(sameDirection) { + context.save(); + context.beginPath(); + context.moveTo(410, 210); + context.lineTo(500, 250); + context.stroke(); + + context.beginPath(); + context.arc(500, 250, 3, 0, Math.PI*2, false); + context.fillStyle = 'navy'; + context.fill(); + + context.font = '16px Lucida Sans'; + context.fillText(sameDirection ? '+1' : '-1', 455, 225); + context.fillText(sameDirection ? '1' : '-1', 515, 255); + context.restore(); +} + +function drawInnerCircleAnnotations(sameDirection) { + context.save(); + context.beginPath(); + context.moveTo(300, 175); + context.lineTo(100, 250); + context.stroke(); + + context.beginPath(); + context.arc(100, 250, 3, 0, Math.PI*2, false); + context.fillStyle = 'navy'; + context.fill(); + + context.font = '16px Lucida Sans'; + context.fillText('+1', 125, 225); + context.fillText(sameDirection ? '+1' : '-1', 215, 185); + context.fillText(sameDirection ? '2' : '0', 75, 255); + context.restore(); +} + +function drawAnnotations(sameDirection) { + context.save(); + context.strokeStyle = 'blue'; + drawInnerCircleAnnotations(sameDirection); + drawOuterCircleAnnotations(sameDirection); + drawArcAnnotations(sameDirection); + context.restore(); +} + +function drawTwoArcs(sameDirection) { + context.beginPath(); + context.arc(300, 170, 150, 0, Math.PI*2, false); // outer: CW + context.arc(300, 170, 100, 0, Math.PI*2, !sameDirection); // innner + + context.fill(); + context.shadowColor = undefined; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.stroke(); +} + +function draw(sameDirection) { + context.clearRect(0, 0, context.canvas.width, + context.canvas.height); + drawGrid('lightgray', 10, 10); + + context.save(); + + context.shadowColor = 'rgba(0, 0, 0, 0.8)'; + context.shadowOffsetX = 12; + context.shadowOffsetY = 12; + context.shadowBlur = 15; + + drawTwoArcs(directionCheckbox.checked); + + context.restore(); + + drawText(); + + if (annotationCheckbox.checked) { + drawAnnotations(directionCheckbox.checked); + } +} + +// Event handlers................................................ + +annotationCheckbox.onclick = function (e) { + draw(directionCheckbox.checked); +}; + +directionCheckbox.onclick = function (e) { + draw(directionCheckbox.checked); +}; + +// Initialization................................................ + +context.fillStyle = 'rgba(100, 140, 230, 0.5)'; +context.strokeStyle = context.fillStyle;//'rgba(20, 60, 150, 0.5)'; + +draw(directionCheckbox.checked); diff --git a/canvas/ch02/example-2.11/example.html b/canvas/ch02/example-2.11/example.html new file mode 100644 index 0000000..e5e5fd4 --- /dev/null +++ b/canvas/ch02/example-2.11/example.html @@ -0,0 +1,68 @@ + + + + + + Various Cutout Shapes + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.11/example.js b/canvas/ch02/example-2.11/example.js new file mode 100644 index 0000000..eeec649 --- /dev/null +++ b/canvas/ch02/example-2.11/example.js @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'); + +// Functions.......................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function draw() { + context.clearRect(0, 0, context.canvas.width, + context.canvas.height); + drawGrid('lightgray', 10, 10); + + context.save(); + + context.shadowColor = 'rgba(200, 200, 0, 0.5)'; + context.shadowOffsetX = 12; + context.shadowOffsetY = 12; + context.shadowBlur = 15; + + drawCutouts(); + strokeCutoutShapes(); + context.restore(); +} + +function drawCutouts() { + context.beginPath(); + addOuterRectanglePath(); // CW + + addCirclePath(); // CCW + addRectanglePath(); // CCW + addTrianglePath(); // CCW + + context.fill(); // Cut out shapes +} + +function strokeCutoutShapes() { + context.save(); + + context.strokeStyle = 'rgba(0,0,0,0.7)'; + + context.beginPath(); + addOuterRectanglePath(); // CW + context.stroke(); + + context.beginPath(); + addCirclePath(); + addRectanglePath(); + addTrianglePath(); + context.stroke(); + + context.restore(); +} + +function rect(x, y, w, h, direction) { + if (direction) { // CCW + context.moveTo(x, y); + context.lineTo(x, y + h); + context.lineTo(x + w, y + h); + context.lineTo(x + w, y); + context.closePath(); + } + else { + context.moveTo(x, y); + context.lineTo(x + w, y); + context.lineTo(x + w, y + h); + context.lineTo(x, y + h); + context.closePath(); + } +} + +function addOuterRectanglePath() { + context.rect(110, 25, 370, 335); +} + +function addCirclePath() { + context.arc(300, 300, 40, 0, Math.PI*2, true); +} + +function addRectanglePath() { + rect(310, 55, 70, 35, true); +} + +function addTrianglePath() { + context.moveTo(400, 200); + context.lineTo(250, 115); + context.lineTo(200, 200); + context.closePath(); +} + +// Initialization..................................................... + +context.fillStyle = 'goldenrod'; +draw(); diff --git a/canvas/ch02/example-2.12/example.html b/canvas/ch02/example-2.12/example.html new file mode 100644 index 0000000..a70406e --- /dev/null +++ b/canvas/ch02/example-2.12/example.html @@ -0,0 +1,59 @@ + + + + + Drawing Lines + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.12/example.js b/canvas/ch02/example-2.12/example.js new file mode 100644 index 0000000..1cc98b5 --- /dev/null +++ b/canvas/ch02/example-2.12/example.js @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'); + +context.lineWidth = 1; +context.beginPath(); +context.moveTo(50, 10); +context.lineTo(450, 10); +context.stroke(); + +context.beginPath(); +context.moveTo(50.5, 50.5); +context.lineTo(450.5, 50.5); +context.stroke(); diff --git a/canvas/ch02/example-2.13/example.html b/canvas/ch02/example-2.13/example.html new file mode 100644 index 0000000..2080847 --- /dev/null +++ b/canvas/ch02/example-2.13/example.html @@ -0,0 +1,61 @@ + + + + + + Drawing a Grid + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.13/example.js b/canvas/ch02/example-2.13/example.js new file mode 100644 index 0000000..e1cdbd6 --- /dev/null +++ b/canvas/ch02/example-2.13/example.js @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'); + +// Functions..................................................... + +function drawGrid(context, color, stepx, stepy) { + context.strokeStyle = color; + context.lineWidth = 0.5; + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } +} + +// Initialization................................................ + +drawGrid(context, 'lightgray', 10, 10); diff --git a/canvas/ch02/example-2.14/example.html b/canvas/ch02/example-2.14/example.html new file mode 100644 index 0000000..4cac303 --- /dev/null +++ b/canvas/ch02/example-2.14/example.html @@ -0,0 +1,61 @@ + + + + + + Drawing Axes + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.14/example.js b/canvas/ch02/example-2.14/example.js new file mode 100644 index 0000000..b70a809 --- /dev/null +++ b/canvas/ch02/example-2.14/example.js @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + AXIS_MARGIN = 40, + AXIS_ORIGIN = { x: AXIS_MARGIN, y: canvas.height-AXIS_MARGIN }, + + AXIS_TOP = AXIS_MARGIN, + AXIS_RIGHT = canvas.width-AXIS_MARGIN, + + HORIZONTAL_TICK_SPACING = 10, + VERTICAL_TICK_SPACING = 10, + + AXIS_WIDTH = AXIS_RIGHT - AXIS_ORIGIN.x, + AXIS_HEIGHT = AXIS_ORIGIN.y - AXIS_TOP, + + NUM_VERTICAL_TICKS = AXIS_HEIGHT / VERTICAL_TICK_SPACING, + NUM_HORIZONTAL_TICKS = AXIS_WIDTH / HORIZONTAL_TICK_SPACING, + + TICK_WIDTH = 10, + TICKS_LINEWIDTH = 0.5, + TICKS_COLOR = 'navy', + + AXIS_LINEWIDTH = 1.0, + AXIS_COLOR = 'blue'; + +// Functions.......................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.fillStyle = 'white'; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + context.lineWidth = 0.5; + context.strokeStyle = color; + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function drawAxes() { + context.save(); + context.strokeStyle = AXIS_COLOR; + context.lineWidth = AXIS_LINEWIDTH; + + drawHorizontalAxis(); + drawVerticalAxis(); + + context.lineWidth = 0.5; + context.lineWidth = TICKS_LINEWIDTH; + context.strokeStyle = TICKS_COLOR; + + drawVerticalAxisTicks(); + drawHorizontalAxisTicks(); + + context.restore(); +} + +function drawHorizontalAxis() { + context.beginPath(); + context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y); + context.lineTo(AXIS_RIGHT, AXIS_ORIGIN.y) + context.stroke(); +} + +function drawVerticalAxis() { + context.beginPath(); + context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y); + context.lineTo(AXIS_ORIGIN.x, AXIS_TOP); + context.stroke(); +} + +function drawVerticalAxisTicks() { + var deltaY; + + for (var i=1; i < NUM_VERTICAL_TICKS; ++i) { + context.beginPath(); + + if (i % 5 === 0) deltaX = TICK_WIDTH; + else deltaX = TICK_WIDTH/2; + + context.moveTo(AXIS_ORIGIN.x - deltaX, + AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING); + + context.lineTo(AXIS_ORIGIN.x + deltaX, + AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING); + + context.stroke(); + } +} + +function drawHorizontalAxisTicks() { + var deltaY; + + for (var i=1; i < NUM_HORIZONTAL_TICKS; ++i) { + context.beginPath(); + + if (i % 5 === 0) deltaY = TICK_WIDTH; + else deltaY = TICK_WIDTH/2; + + context.moveTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, + AXIS_ORIGIN.y - deltaY); + + context.lineTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, + AXIS_ORIGIN.y + deltaY); + + context.stroke(); + } +} + +// Initialization................................................ + +drawGrid('lightgray', 10, 10); +drawAxes(); diff --git a/canvas/ch02/example-2.15/example.html b/canvas/ch02/example-2.15/example.html new file mode 100644 index 0000000..138fcdd --- /dev/null +++ b/canvas/ch02/example-2.15/example.html @@ -0,0 +1,82 @@ + + + + + + Drawing Lines with Rubber Bands + + + + + + + Canvas not supported + + +
+ Stroke color: + Guidewires: + +
+ + + + diff --git a/canvas/ch02/example-2.15/example.js b/canvas/ch02/example-2.15/example.js new file mode 100644 index 0000000..405cede --- /dev/null +++ b/canvas/ch02/example-2.15/example.js @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + eraseAllButton = document.getElementById('eraseAllButton'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + guidewireCheckbox = document.getElementById('guidewireCheckbox'), + drawingSurfaceImageData, + mousedown = {}, + rubberbandRect = {}, + dragging = false, + guidewires = guidewireCheckbox.checked; + +// Functions.......................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.lineWidth = 0.5; + context.clearRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) }; +} + +// Save and restore drawing surface................................... + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +function restoreDrawingSurface() { + context.putImageData(drawingSurfaceImageData, 0, 0); +} + +// Rubberbands........................................................ + +function updateRubberbandRectangle(loc) { + rubberbandRect.width = Math.abs(loc.x - mousedown.x); + rubberbandRect.height = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x; + else rubberbandRect.left = loc.x; + + if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y; + else rubberbandRect.top = loc.y; + + context.save(); + context.strokeStyle = 'red'; + context.restore(); +} + +function drawRubberbandShape(loc) { + context.beginPath(); + context.moveTo(mousedown.x, mousedown.y); + context.lineTo(loc.x, loc.y); + context.stroke(); +} + +function updateRubberband(loc) { + updateRubberbandRectangle(loc); + drawRubberbandShape(loc); +} + +// Guidewires......................................................... + +function drawHorizontalLine (y) { + context.beginPath(); + context.moveTo(0,y+0.5); + context.lineTo(context.canvas.width,y+0.5); + context.stroke(); +} + +function drawVerticalLine (x) { + context.beginPath(); + context.moveTo(x+0.5,0); + context.lineTo(x+0.5,context.canvas.height); + context.stroke(); +} + +function drawGuidewires(x, y) { + context.save(); + context.strokeStyle = 'rgba(0,0,230,0.4)'; + context.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + context.restore(); +} + +// Canvas event handlers.............................................. + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + e.preventDefault(); // prevent cursor change + + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; + dragging = true; +}; + +canvas.onmousemove = function (e) { + var loc; + + if (dragging) { + e.preventDefault(); // prevent selections + + loc = windowToCanvas(e.clientX, e.clientY); + restoreDrawingSurface(); + updateRubberband(loc); + + if(guidewires) { + drawGuidewires(loc.x, loc.y); + } + } +}; + +canvas.onmouseup = function (e) { + loc = windowToCanvas(e.clientX, e.clientY); + restoreDrawingSurface(); + updateRubberband(loc); + dragging = false; +}; + +// Controls event handlers....................................... + +eraseAllButton.onclick = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + saveDrawingSurface(); +}; + +strokeStyleSelect.onchange = function (e) { + context.strokeStyle = strokeStyleSelect.value; +}; + +guidewireCheckbox.onchange = function (e) { + guidewires = guidewireCheckbox.checked; +}; + +// Initialization................................................ + +context.strokeStyle = strokeStyleSelect.value; +drawGrid('lightgray', 10, 10); diff --git a/canvas/ch02/example-2.17/example.html b/canvas/ch02/example-2.17/example.html new file mode 100644 index 0000000..145f7de --- /dev/null +++ b/canvas/ch02/example-2.17/example.html @@ -0,0 +1,59 @@ + + + + + Dashed lines + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.17/example.js b/canvas/ch02/example-2.17/example.js new file mode 100644 index 0000000..859d342 --- /dev/null +++ b/canvas/ch02/example-2.17/example.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.querySelector('#canvas').getContext('2d'); + +function drawDashedLine(context, x1, y1, x2, y2, dashLength) { + dashLength = dashLength === undefined ? 5 : dashLength; + + var deltaX = x2 - x1; + var deltaY = y2 - y1; + var numDashes = Math.floor( + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / dashLength); + + for (var i=0; i < numDashes; ++i) { + context[ i % 2 === 0 ? 'moveTo' : 'lineTo' ] + (x1 + (deltaX / numDashes) * i, y1 + (deltaY / numDashes) * i); + } + + context.stroke(); +}; + +context.lineWidth = 3; +context.strokeStyle = 'blue'; + +drawDashedLine(context, 20, 20, context.canvas.width-20, 20); +drawDashedLine(context, context.canvas.width-20, 20, context.canvas.width-20, context.canvas.height-20, 10); +drawDashedLine(context, context.canvas.width-20, context.canvas.height-20, 20, context.canvas.height-20, 15); +drawDashedLine(context, 20, context.canvas.height-20, 20, 20, 2); diff --git a/canvas/ch02/example-2.18/example.html b/canvas/ch02/example-2.18/example.html new file mode 100644 index 0000000..a8e1fe3 --- /dev/null +++ b/canvas/ch02/example-2.18/example.html @@ -0,0 +1,59 @@ + + + + + Extending CanvasRenderingContext2D to Draw Dashed lines + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.18/example.js b/canvas/ch02/example-2.18/example.js new file mode 100644 index 0000000..16e50a9 --- /dev/null +++ b/canvas/ch02/example-2.18/example.js @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'), + moveToFunction = CanvasRenderingContext2D.prototype.moveTo; + +CanvasRenderingContext2D.prototype.lastMoveToLocation = {}; + +CanvasRenderingContext2D.prototype.moveTo = function (x, y) { + moveToFunction.apply(context, [x,y]); + this.lastMoveToLocation.x = x; + this.lastMoveToLocation.y = y; +}; + +CanvasRenderingContext2D.prototype.dashedLineTo = function (x, y, dashLength) { + dashLength = dashLength === undefined ? 5 : dashLength; + + var startX = this.lastMoveToLocation.x; + var startY = this.lastMoveToLocation.y; + + var deltaX = x - startX; + var deltaY = y - startY; + var numDashes = Math.floor(Math.sqrt(deltaX * deltaX + deltaY * deltaY) / dashLength); + + for (var i=0; i < numDashes; ++i) { + this[ i % 2 === 0 ? 'moveTo' : 'lineTo' ] + (startX + (deltaX / numDashes) * i, startY + (deltaY / numDashes) * i); + } + + this.moveTo(x, y); +}; + +context.lineWidth = 3; +context.strokeStyle = 'blue'; + +context.moveTo(20, 20); +context.dashedLineTo(context.canvas.width-20, 20); +context.dashedLineTo(context.canvas.width-20, context.canvas.height-20); +context.dashedLineTo(20, context.canvas.height-20); +context.dashedLineTo(20, 20); +context.dashedLineTo(context.canvas.width-20, context.canvas.height-20); +context.stroke(); diff --git a/canvas/ch02/example-2.19/example.html b/canvas/ch02/example-2.19/example.html new file mode 100644 index 0000000..9302919 --- /dev/null +++ b/canvas/ch02/example-2.19/example.html @@ -0,0 +1,107 @@ + + + + + + Drawing Circles with Rubber Bands + + + + + + + Canvas not supported + + +
+ Stroke color: + + Fill color: + + Line width: + + Fill + + Guidewires: + + +
+ + + + diff --git a/canvas/ch02/example-2.19/example.js b/canvas/ch02/example-2.19/example.js new file mode 100644 index 0000000..2417161 --- /dev/null +++ b/canvas/ch02/example-2.19/example.js @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + eraseAllButton = document.getElementById('eraseAllButton'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + lineWidthSelect = document.getElementById('lineWidthSelect'), + fillCheckbox = document.getElementById('fillCheckbox'), + guidewireCheckbox = document.getElementById('guidewireCheckbox'), + + drawingSurfaceImageData, + + mousedown = {}, + rubberbandRect = {}, + dragging = false, + guidewires = true; + + +// General-purpose functions..................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Save and restore drawing surface.............................. + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +function restoreDrawingSurface() { + context.putImageData(drawingSurfaceImageData, 0, 0); +} + +// Rubberbands................................................... + +function updateRubberbandRectangle(loc) { + rubberbandRect.width = Math.abs(loc.x - mousedown.x); + rubberbandRect.height = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x; + else rubberbandRect.left = loc.x; + + if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y; + else rubberbandRect.top = loc.y; +} + +function drawRubberbandShape(loc) { + var angle, + radius; + + if (mousedown.y === loc.y) { // horizontal line + // Horizontal lines are a special case. See the else + // block for an explanation + + radius = Math.abs(loc.x - mousedown.x); + } + else { + // For horizontal lines, the angle is 0, and Math.sin(0) + // is 0, which means we would be dividing by 0 here to get NaN + // for radius. The if block above catches horizontal lines. + + angle = Math.atan(rubberbandRect.height/rubberbandRect.width), + radius = rubberbandRect.height / Math.sin(angle); + } + + context.beginPath(); + context.arc(mousedown.x, mousedown.y, radius, 0, Math.PI*2, false); + context.stroke(); + + if (fillCheckbox.checked) + context.fill(); +} + +function updateRubberband(loc) { + updateRubberbandRectangle(loc); + drawRubberbandShape(loc); +} + +// Guidewires.................................................... + +function drawHorizontalLine (y) { + context.beginPath(); + context.moveTo(0,y+0.5); + context.lineTo(context.canvas.width,y+0.5); + context.stroke(); +} + +function drawVerticalLine (x) { + context.beginPath(); + context.moveTo(x+0.5,0); + context.lineTo(x+0.5,context.canvas.height); + context.stroke(); +} + +function drawGuidewires(x, y) { + context.save(); + context.strokeStyle = 'rgba(0,0,230,0.4)'; + context.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + context.restore(); +} + +// Canvas event handlers......................................... + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + e.preventDefault(); // prevent cursor change + + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; + dragging = true; +}; + +canvas.onmousemove = function (e) { + var loc; + + if (dragging) { + e.preventDefault(); // prevent selections + + loc = windowToCanvas(e.clientX, e.clientY); + restoreDrawingSurface(); + updateRubberband(loc); + + if(guidewires) { + drawGuidewires(loc.x, loc.y); + } + } +}; + +canvas.onmouseup = function (e) { + loc = windowToCanvas(e.clientX, e.clientY); + + restoreDrawingSurface(); + updateRubberband(loc); + dragging = false; +}; + +// Controls event handlers....................................... + +eraseAllButton.onclick = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + saveDrawingSurface(); +}; + +strokeStyleSelect.onchange = function (e) { + context.strokeStyle = strokeStyleSelect.value; +}; + +fillStyleSelect.onchange = function (e) { + context.fillStyle = fillStyleSelect.value; +}; + +lineWidthSelect.onchange = function (e) { + context.lineWidth = lineWidthSelect.value; +}; + + +guidewireCheckbox.onchange = function (e) { + guidewires = guidewireCheckbox.checked; +}; + +// Initialization................................................ + +context.strokeStyle = strokeStyleSelect.value; +context.fillStyle = fillStyleSelect.value; +context.lineWidth = lineWidthSelect.value; +drawGrid('lightgray', 10, 10); diff --git a/canvas/ch02/example-2.2/example.html b/canvas/ch02/example-2.2/example.html new file mode 100644 index 0000000..58c9a5a --- /dev/null +++ b/canvas/ch02/example-2.2/example.html @@ -0,0 +1,55 @@ + + + + + Colors and Transparency + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.2/example.js b/canvas/ch02/example-2.2/example.js new file mode 100644 index 0000000..5ce7004 --- /dev/null +++ b/canvas/ch02/example-2.2/example.js @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'); + +context.lineJoin = 'round'; +context.lineWidth = 30; + +context.font = '24px Helvetica'; +context.fillText('Click anywhere to erase', 175, 200); + +context.strokeStyle = 'goldenrod'; +context.fillStyle = 'rgba(0, 0, 255, 0.5)'; + +context.strokeRect(75, 100, 200, 200); +context.fillRect(325, 100, 200, 200); + +context.canvas.onmousedown = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); +}; diff --git a/canvas/ch02/example-2.20/example.html b/canvas/ch02/example-2.20/example.html new file mode 100644 index 0000000..5defc78 --- /dev/null +++ b/canvas/ch02/example-2.20/example.html @@ -0,0 +1,54 @@ + + + + + + Rounded Rectangles + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.20/example.js b/canvas/ch02/example-2.20/example.js new file mode 100644 index 0000000..c14940f --- /dev/null +++ b/canvas/ch02/example-2.20/example.js @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'); + +function roundedRect(cornerX, cornerY, width, height, cornerRadius) { + if (width > 0) context.moveTo(cornerX + cornerRadius, cornerY); + else context.moveTo(cornerX - cornerRadius, cornerY); + + context.arcTo(cornerX + width, cornerY, + cornerX + width, cornerY + height, + cornerRadius); + + context.arcTo(cornerX + width, cornerY + height, + cornerX, cornerY + height, + cornerRadius); + + context.arcTo(cornerX, cornerY + height, + cornerX, cornerY, + cornerRadius); + + if (width > 0) { + context.arcTo(cornerX, cornerY, + cornerX + cornerRadius, cornerY, + cornerRadius); + } + else { + context.arcTo(cornerX, cornerY, + cornerX - cornerRadius, cornerY, + cornerRadius); + } +} + +function drawRoundedRect(strokeStyle, fillStyle, cornerX, cornerY, width, height, cornerRadius) { + context.beginPath(); + + roundedRect(cornerX, cornerY, width, height, cornerRadius); + + context.strokeStyle = strokeStyle; + context.fillStyle = fillStyle; + + context.stroke(); + context.fill(); +} + +drawRoundedRect('blue', 'yellow', 50, 40, 100, 100, 10); +drawRoundedRect('purple', 'green', 275, 40, -100, 100, 20); +drawRoundedRect('red', 'white', 300, 140, 100, -100, 30); +drawRoundedRect('white', 'blue', 525, 140, -100, -100, 40); diff --git a/canvas/ch02/example-2.21/example.html b/canvas/ch02/example-2.21/example.html new file mode 100644 index 0000000..e0865d7 --- /dev/null +++ b/canvas/ch02/example-2.21/example.html @@ -0,0 +1,62 @@ + + + + + + A Dial Showing the Degrees of a Circle + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.21/example.js b/canvas/ch02/example-2.21/example.js new file mode 100644 index 0000000..e0b1496 --- /dev/null +++ b/canvas/ch02/example-2.21/example.js @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + CENTROID_RADIUS = 10, + CENTROID_STROKE_STYLE = 'rgba(0, 0, 0, 0.5)', + CENTROID_FILL_STYLE = 'rgba(80, 190, 240, 0.6)', + + RING_INNER_RADIUS = 35, + RING_OUTER_RADIUS = 55, + + ANNOTATIONS_FILL_STYLE = 'rgba(0, 0, 230, 0.9)', + ANNOTATIONS_TEXT_SIZE = 12, + + TICK_WIDTH = 10, + TICK_LONG_STROKE_STYLE = 'rgba(100, 140, 230, 0.9)', + TICK_SHORT_STROKE_STYLE = 'rgba(100, 140, 230, 0.7)', + + TRACKING_DIAL_STROKING_STYLE = 'rgba(100, 140, 230, 0.5)', + + GUIDEWIRE_STROKE_STYLE = 'goldenrod', + GUIDEWIRE_FILL_STYLE = 'rgba(250, 250, 0, 0.6)', + + circle = { x: canvas.width/2, + y: canvas.height/2, + radius: 150 + }; + +// Functions.......................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, + context.canvas.height); + + for (var i = stepx + 0.5; + i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; + i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function drawDial() { + var loc = {x: circle.x, y: circle.y}; + + drawCentroid(); + drawCentroidGuidewire(loc); + + drawRing(); + drawTickInnerCircle(); + drawTicks(); + drawAnnotations(); +} + +function drawCentroid() { + context.beginPath(); + context.save(); + context.strokeStyle = CENTROID_STROKE_STYLE; + context.fillStyle = CENTROID_FILL_STYLE; + context.arc(circle.x, circle.y, + CENTROID_RADIUS, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + context.restore(); +} + +function drawCentroidGuidewire(loc) { + var angle = -Math.PI/4, + radius, endpt; + + radius = circle.radius + RING_OUTER_RADIUS; + + if (loc.x >= circle.x) { + endpt = { x: circle.x + radius * Math.cos(angle), + y: circle.y + radius * Math.sin(angle) + }; + } + else { + endpt = { x: circle.x - radius * Math.cos(angle), + y: circle.y - radius * Math.sin(angle) + }; + } + + context.save(); + + context.strokeStyle = GUIDEWIRE_STROKE_STYLE; + context.fillStyle = GUIDEWIRE_FILL_STYLE; + + context.beginPath(); + context.moveTo(circle.x, circle.y); + context.lineTo(endpt.x, endpt.y); + context.stroke(); + + context.beginPath(); + context.strokeStyle = TICK_LONG_STROKE_STYLE; + context.arc(endpt.x, endpt.y, 5, 0, Math.PI*2, false); + context.fill(); + context.stroke(); + + context.restore(); +} + +function drawRing() { + drawRingOuterCircle(); + + context.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + context.arc(circle.x, circle.y, + circle.radius + RING_INNER_RADIUS, + 0, Math.PI*2, false); + + context.fillStyle = 'rgba(100, 140, 230, 0.1)'; + context.fill(); + context.stroke(); +} + +function drawRingOuterCircle() { + context.shadowColor = 'rgba(0, 0, 0, 0.7)'; + context.shadowOffsetX = 3, + context.shadowOffsetY = 3, + context.shadowBlur = 6, + context.strokeStyle = TRACKING_DIAL_STROKING_STYLE; + context.beginPath(); + context.arc(circle.x, circle.y, circle.radius + + RING_OUTER_RADIUS, 0, Math.PI*2, true); + context.stroke(); +} + +function drawTickInnerCircle() { + context.save(); + context.beginPath(); + context.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + context.arc(circle.x, circle.y, + circle.radius + RING_INNER_RADIUS - TICK_WIDTH, + 0, Math.PI*2, false); + context.stroke(); + context.restore(); +} + +function drawTick(angle, radius, cnt) { + var tickWidth = cnt % 4 === 0 ? TICK_WIDTH : TICK_WIDTH/2; + + context.beginPath(); + + context.moveTo(circle.x + Math.cos(angle) * (radius - tickWidth), + circle.y + Math.sin(angle) * (radius - tickWidth)); + + context.lineTo(circle.x + Math.cos(angle) * (radius), + circle.y + Math.sin(angle) * (radius)); + + context.strokeStyle = TICK_SHORT_STROKE_STYLE; + context.stroke(); +} + +function drawTicks() { + var radius = circle.radius + RING_INNER_RADIUS, + ANGLE_MAX = 2*Math.PI, + ANGLE_DELTA = Math.PI/64, + tickWidth; + + context.save(); + + for (var angle = 0, cnt = 0; angle < ANGLE_MAX; + angle += ANGLE_DELTA, cnt++) { + drawTick(angle, radius, cnt++); + } + + context.restore(); +} + +function drawAnnotations() { + var radius = circle.radius + RING_INNER_RADIUS; + + context.save(); + context.fillStyle = ANNOTATIONS_FILL_STYLE; + context.font = ANNOTATIONS_TEXT_SIZE + 'px Helvetica'; + + for (var angle=0; angle < 2*Math.PI; angle += Math.PI/8) { + context.beginPath(); + context.fillText((angle * 180 / Math.PI).toFixed(0), + circle.x + Math.cos(angle) * (radius - TICK_WIDTH*2), + circle.y - Math.sin(angle) * (radius - TICK_WIDTH*2)); + } + context.restore(); +} + +// Initialization.................................................... + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; + +context.textAlign = 'center'; +context.textBaseline = 'middle'; +drawGrid('lightgray', 10, 10); +drawDial(); diff --git a/canvas/ch02/example-2.22/example.html b/canvas/ch02/example-2.22/example.html new file mode 100644 index 0000000..68ce9f3 --- /dev/null +++ b/canvas/ch02/example-2.22/example.html @@ -0,0 +1,68 @@ + + + + + Bezier Curves: Quadratic + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.22/example.js b/canvas/ch02/example-2.22/example.js new file mode 100644 index 0000000..20027b0 --- /dev/null +++ b/canvas/ch02/example-2.22/example.js @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'); + +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'yellow'; + +context.shadowColor = 'rgba(50, 50, 50, 1.0)'; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; + +context.lineWidth = 20; +context.lineCap = 'round'; + +context.beginPath(); +context.moveTo(120.5, 130); +context.quadraticCurveTo(150.8, 130, 160.6, 150.5); +context.quadraticCurveTo(190, 250.0, 210.5, 160.5); +context.quadraticCurveTo(240, 100.5, 290, 70.5); +context.stroke(); + diff --git a/canvas/ch02/example-2.23/example.html b/canvas/ch02/example-2.23/example.html new file mode 100644 index 0000000..8ca1d80 --- /dev/null +++ b/canvas/ch02/example-2.23/example.html @@ -0,0 +1,67 @@ + + + + + Quadratic Curves + + + + + + + canvas not supported + + + + + diff --git a/canvas/ch02/example-2.23/example.js b/canvas/ch02/example-2.23/example.js new file mode 100644 index 0000000..9f89bd3 --- /dev/null +++ b/canvas/ch02/example-2.23/example.js @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + ARROW_MARGIN = 30, + POINT_RADIUS = 7, + points = [ + { x: canvas.width - ARROW_MARGIN, + y: canvas.height - ARROW_MARGIN }, + + { x: canvas.width - ARROW_MARGIN*2, + y: canvas.height - ARROW_MARGIN }, + + { x: POINT_RADIUS, + y: canvas.height/2 }, + + { x: ARROW_MARGIN, + y: canvas.height/2 - ARROW_MARGIN }, + + { x: canvas.width - ARROW_MARGIN, + y: ARROW_MARGIN }, + + { x: canvas.width - ARROW_MARGIN, + y: ARROW_MARGIN*2 }, + ]; + +// Functions.......................................................... + +function drawPoint(x, y, strokeStyle, fillStyle) { + context.beginPath(); + context.fillStyle = fillStyle; + context.strokeStyle = strokeStyle; + context.lineWidth = 0.5; + context.arc(x, y, POINT_RADIUS, 0, Math.PI*2, false); + context.fill(); + context.stroke(); +} + +function drawBezierPoints() { + var i, + strokeStyle, + fillStyle; + + for (i=0; i < points.length; ++i) { + fillStyle = i % 2 === 0 ? 'white' : 'blue', + strokeStyle = i % 2 === 0 ? 'blue' : 'white'; + + drawPoint(points[i].x, points[i].y, + strokeStyle, fillStyle); + } +} + +function drawArrow() { + context.strokeStyle = 'white'; + context.fillStyle = 'cornflowerblue'; + + context.moveTo(canvas.width - ARROW_MARGIN, + ARROW_MARGIN*2); + + context.lineTo(canvas.width - ARROW_MARGIN, + canvas.height - ARROW_MARGIN*2); + + context.quadraticCurveTo(points[0].x, points[0].y, + points[1].x, points[1].y); + + context.lineTo(ARROW_MARGIN, + canvas.height/2 + ARROW_MARGIN); + + context.quadraticCurveTo(points[2].x, points[2].y, + points[3].x, points[3].y); + + context.lineTo(canvas.width - ARROW_MARGIN*2, + ARROW_MARGIN); + + context.quadraticCurveTo(points[4].x, points[4].y, + points[5].x, points[5].y); + context.fill(); + context.stroke(); +} + +// Initialization..................................................... + +context.clearRect(0,0,canvas.width,canvas.height); +drawArrow(); +drawBezierPoints(); diff --git a/canvas/ch02/example-2.24/example.html b/canvas/ch02/example-2.24/example.html new file mode 100644 index 0000000..dd5d2b8 --- /dev/null +++ b/canvas/ch02/example-2.24/example.html @@ -0,0 +1,61 @@ + + + + + + Bezier Curves + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.24/example.js b/canvas/ch02/example-2.24/example.js new file mode 100644 index 0000000..bf0106b --- /dev/null +++ b/canvas/ch02/example-2.24/example.js @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + endPoints = [ + { x: 130, y: 70 }, + { x: 430, y: 270 }, + ], + controlPoints = [ + { x: 130, y: 250 }, + { x: 450, y: 70 }, + ]; + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function drawBezierCurve() { + context.strokeStyle = 'blue'; + context.fillStyle = 'yellow'; + + context.beginPath(); + context.moveTo(endPoints[0].x, endPoints[0].y); + context.bezierCurveTo(controlPoints[0].x, controlPoints[0].y, + controlPoints[1].x, controlPoints[1].y, + endPoints[1].x, endPoints[1].y); + context.stroke(); +} + +function drawEndPoints() { + context.strokeStyle = 'blue'; + context.fillStyle = 'red'; + + endPoints.forEach( function (point) { + context.beginPath(); + context.arc(point.x, point.y, 5, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + }); +} + +function drawControlPoints() { + context.strokeStyle = 'yellow'; + context.fillStyle = 'blue'; + + controlPoints.forEach( function (point) { + context.beginPath(); + context.arc(point.x, point.y, 5, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + }); +} + +drawGrid('lightgray', 10, 10); + +drawControlPoints(); +drawEndPoints(); +drawBezierCurve(); diff --git a/canvas/ch02/example-2.25/example.html b/canvas/ch02/example-2.25/example.html new file mode 100644 index 0000000..f1bc736 --- /dev/null +++ b/canvas/ch02/example-2.25/example.html @@ -0,0 +1,111 @@ + + + + + + Drawing Polygons + + + + + + + Canvas not supported + + +
+ Stroke color: + + Fill color: + + Sides: + + Start angle: + + Fill + +
+ + + + diff --git a/canvas/ch02/example-2.25/example.js b/canvas/ch02/example-2.25/example.js new file mode 100644 index 0000000..e84dfb6 --- /dev/null +++ b/canvas/ch02/example-2.25/example.js @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + eraseAllButton = document.getElementById('eraseAllButton'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + startAngleSelect = document.getElementById('startAngleSelect'), + + fillStyleSelect = document.getElementById('fillStyleSelect'), + fillCheckbox = document.getElementById('fillCheckbox'), + + sidesSelect = document.getElementById('sidesSelect'), + + drawingSurfaceImageData, + + mousedown = {}, + rubberbandRect = {}, + dragging = false, + + sides = 8, + startAngle = 0, + + guidewires = true, + + Point = function (x, y) { + this.x = x; + this.y = y; + }; + + +// Functions..................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Save and restore drawing surface.............................. + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +function restoreDrawingSurface() { + context.putImageData(drawingSurfaceImageData, 0, 0); +} + +// Rubberbands................................................... + +function updateRubberbandRectangle(loc) { + rubberbandRect.width = Math.abs(loc.x - mousedown.x); + rubberbandRect.height = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x; + else rubberbandRect.left = loc.x; + + if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y; + else rubberbandRect.top = loc.y; +} + +function getPolygonPoints(centerX, centerY, radius, sides, startAngle) { + var points = [], + angle = startAngle || 0; + + for (var i=0; i < sides; ++i) { + points.push(new Point(centerX + radius * Math.sin(angle), + centerY - radius * Math.cos(angle))); + angle += 2*Math.PI/sides; + } + + return points; +} + +function createPolygonPath(centerX, centerY, radius, sides, startAngle) { + var points = getPolygonPoints(centerX, centerY, radius, sides, startAngle); + + context.beginPath(); + + context.moveTo(points[0].x, points[0].y); + + for (var i=1; i < sides; ++i) { + context.lineTo(points[i].x, points[i].y); + } + + context.closePath(); +} + +function drawRubberbandShape(loc, sides, startAngle) { + createPolygonPath(mousedown.x, mousedown.y, + rubberbandRect.width, + parseInt(sidesSelect.value), + (Math.PI / 180) * parseInt(startAngleSelect.value)); + context.stroke(); + + if (fillCheckbox.checked) { + context.fill(); + } +} + +function updateRubberband(loc, sides, startAngle) { + updateRubberbandRectangle(loc); + drawRubberbandShape(loc, sides, startAngle); +} + +// Guidewires.................................................... + +function drawHorizontalLine (y) { + context.beginPath(); + context.moveTo(0,y+0.5); + context.lineTo(context.canvas.width,y+0.5); + context.stroke(); +} + +function drawVerticalLine (x) { + context.beginPath(); + context.moveTo(x+0.5,0); + context.lineTo(x+0.5,context.canvas.height); + context.stroke(); +} + +function drawGuidewires(x, y) { + context.save(); + context.strokeStyle = 'rgba(0,0,230,0.4)'; + context.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + context.restore(); +} + +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e); + + saveDrawingSurface(); + + e.preventDefault(); // prevent cursor change + + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; + dragging = true; +}; + +canvas.onmousemove = function (e) { + var loc; + + if (dragging) { + e.preventDefault(); // prevent selections + + loc = windowToCanvas(e); + restoreDrawingSurface(); + updateRubberband(loc, sides, startAngle); + + if (guidewires) { + drawGuidewires(mousedown.x, mousedown.y); + } + } +}; + +canvas.onmouseup = function (e) { + var loc = windowToCanvas(e); + dragging = false; + restoreDrawingSurface(); + updateRubberband(loc); +}; + +eraseAllButton.onclick = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + saveDrawingSurface(); +}; + +strokeStyleSelect.onchange = function (e) { + context.strokeStyle = strokeStyleSelect.value; +}; + +fillStyleSelect.onchange = function (e) { + context.fillStyle = fillStyleSelect.value; +}; + +// Initialization................................................ + +context.strokeStyle = strokeStyleSelect.value; +context.fillStyle = fillStyleSelect.value; +drawGrid('lightgray', 10, 10); diff --git a/canvas/ch02/example-2.26/example.html b/canvas/ch02/example-2.26/example.html new file mode 100644 index 0000000..bbb1e82 --- /dev/null +++ b/canvas/ch02/example-2.26/example.html @@ -0,0 +1,114 @@ + + + + + + Polygon Objects + + + + + + + Canvas not supported + + +
+ Stroke color: + + Fill color: + + Sides: + + + Start angle: + + Fill + +
+ + + + + diff --git a/canvas/ch02/example-2.26/example.js b/canvas/ch02/example-2.26/example.js new file mode 100644 index 0000000..29d6cc9 --- /dev/null +++ b/canvas/ch02/example-2.26/example.js @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + eraseAllButton = document.getElementById('eraseAllButton'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + startAngleSelect = document.getElementById('startAngleSelect'), + + fillStyleSelect = document.getElementById('fillStyleSelect'), + fillCheckbox = document.getElementById('fillCheckbox'), + + sidesSelect = document.getElementById('sidesSelect'), + + drawingSurfaceImageData, + + mousedown = {}, + rubberbandRect = {}, + + dragging = false, + draggingOffsetX, + draggingOffsetY, + + sides = 8, + startAngle = 0, + + guidewires = true, + + polygons = [], + tmpPolygon = new Polygon(0,0,1,4,0,'blue','red',false), + + Point = function (x, y) { + this.x = x; + this.y = y; + }; + + +// Functions..................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Save and restore drawing surface.............................. + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +function restoreDrawingSurface() { + context.putImageData(drawingSurfaceImageData, 0, 0); +} + +// Rubberbands................................................... + +function updateRubberbandRectangle(loc) { + rubberbandRect.width = Math.abs(loc.x - mousedown.x); + rubberbandRect.height = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x; + else rubberbandRect.left = loc.x; + + if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y; + else rubberbandRect.top = loc.y; +} + +function drawRubberbandShape(loc, sides, startAngle) { + var polygon = new Polygon(mousedown.x, mousedown.y, + rubberbandRect.width, + parseInt(sidesSelect.value), + (Math.PI / 180) * parseInt(startAngleSelect.value), + context.strokeStyle, + context.fillStyle, + fillCheckbox.checked); + + context.beginPath(); + polygon.createPath(context); + polygon.stroke(context); + + if (fillCheckbox.checked) { + polygon.fill(context); + } + + if (!dragging) { + polygons.push(polygon); + } +} + +function updateRubberband(loc, sides, startAngle) { + updateRubberbandRectangle(loc); + drawRubberbandShape(loc, sides, startAngle); +} + +// Guidewires.................................................... + +function drawHorizontalLine (y) { + context.beginPath(); + context.moveTo(0,y+0.5); + context.lineTo(context.canvas.width,y+0.5); + context.stroke(); +} + +function drawVerticalLine (x) { + context.beginPath(); + context.moveTo(x+0.5,0); + context.lineTo(x+0.5,context.canvas.height); + context.stroke(); +} + +function drawGuidewires(x, y) { + context.save(); + context.strokeStyle = 'rgba(0,0,230,0.4)'; + context.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + context.restore(); +} + +function drawPolygons() { + polygons.forEach( function (polygon) { + polygon.stroke(context); + if (polygon.filled) { + polygon.fill(context); + } + }); +} + +function startDragging(loc) { + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; +} + +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + e.preventDefault(); // prevent cursor change + + startDragging(loc); + dragging = true; +}; + +canvas.onmousemove = function (e) { + var loc = windowToCanvas(e.clientX, e.clientYe); + + e.preventDefault(); // prevent selections + + if (dragging) { + restoreDrawingSurface(); + updateRubberband(loc, sides, startAngle); + + if (guidewires) { + drawGuidewires(mousedown.x, mousedown.y); + } + } +}; + +canvas.onmouseup = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + dragging = false; + restoreDrawingSurface(); + updateRubberband(loc); +}; + +eraseAllButton.onclick = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + saveDrawingSurface(); +}; + +strokeStyleSelect.onchange = function (e) { + context.strokeStyle = strokeStyleSelect.value; +}; + +fillStyleSelect.onchange = function (e) { + context.fillStyle = fillStyleSelect.value; +}; + +// Initialization................................................ + +context.strokeStyle = strokeStyleSelect.value; +context.fillStyle = fillStyleSelect.value; + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; + +drawGrid('lightgray', 10, 10); diff --git a/canvas/ch02/example-2.28/example.html b/canvas/ch02/example-2.28/example.html new file mode 100644 index 0000000..7294db0 --- /dev/null +++ b/canvas/ch02/example-2.28/example.html @@ -0,0 +1,124 @@ + + + + + + Dragging Polygons + + + + + + + Canvas not supported + + +
+ Stroke color: + + Fill color: + + Sides: + + + Start angle: + + Fill + +
+ +
+ Edit: +
+ + + + + diff --git a/canvas/ch02/example-2.28/example.js b/canvas/ch02/example-2.28/example.js new file mode 100644 index 0000000..5aefea8 --- /dev/null +++ b/canvas/ch02/example-2.28/example.js @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + eraseAllButton = document.getElementById('eraseAllButton'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + startAngleSelect = document.getElementById('startAngleSelect'), + + fillStyleSelect = document.getElementById('fillStyleSelect'), + fillCheckbox = document.getElementById('fillCheckbox'), + editCheckbox = document.getElementById('editCheckbox'), + + sidesSelect = document.getElementById('sidesSelect'), + + drawingSurfaceImageData, + + mousedown = {}, + rubberbandRect = {}, + + dragging = false, + draggingOffsetX, + draggingOffsetY, + + sides = 8, + startAngle = 0, + + guidewires = true, + + editing = false, + polygons = []; + +// Functions.......................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + context.beginPath(); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + } + context.stroke(); + + context.beginPath(); + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + } + context.stroke(); + + context.restore(); +} + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Save and restore drawing surface................................... + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +function restoreDrawingSurface() { + context.putImageData(drawingSurfaceImageData, 0, 0); +} + +// Draw a polygon..................................................... + +function drawPolygon(polygon) { + context.beginPath(); + polygon.createPath(context); + polygon.stroke(context); + + if (fillCheckbox.checked) { + polygon.fill(context); + } +} + +// Rubberbands........................................................ + +function updateRubberbandRectangle(loc) { + rubberbandRect.width = Math.abs(loc.x - mousedown.x); + rubberbandRect.height = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x; + else rubberbandRect.left = loc.x; + + if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y; + else rubberbandRect.top = loc.y; +} + +function drawRubberbandShape(loc, sides, startAngle) { + var polygon = new Polygon(mousedown.x, mousedown.y, + rubberbandRect.width, + parseInt(sidesSelect.value), + (Math.PI / 180) * parseInt(startAngleSelect.value), + context.strokeStyle, + context.fillStyle, + fillCheckbox.checked); + drawPolygon(polygon); + + if (!dragging) { + polygons.push(polygon); + } +} + +function updateRubberband(loc, sides, startAngle) { + updateRubberbandRectangle(loc); + drawRubberbandShape(loc, sides, startAngle); +} + +// Guidewires......................................................... + +function drawHorizontalLine (y) { + context.beginPath(); + context.moveTo(0,y+0.5); + context.lineTo(context.canvas.width,y+0.5); + context.stroke(); +} + +function drawVerticalLine (x) { + context.beginPath(); + context.moveTo(x+0.5,0); + context.lineTo(x+0.5,context.canvas.height); + context.stroke(); +} + +function drawGuidewires(x, y) { + context.save(); + context.strokeStyle = 'rgba(0,0,230,0.4)'; + context.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + context.restore(); +} + +function drawPolygons() { + polygons.forEach( function (polygon) { + drawPolygon(polygon); + }); +} + +// Dragging........................................................... + +function startDragging(loc) { + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; +} + +function startEditing() { + canvas.style.cursor = 'pointer'; + editing = true; +} + +function stopEditing() { + canvas.style.cursor = 'crosshair'; + editing = false; +} + +// Event handlers..................................................... + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + e.preventDefault(); // prevent cursor change + + if (editing) { + polygons.forEach( function (polygon) { + polygon.createPath(context); + if (context.isPointInPath(loc.x, loc.y)) { + startDragging(loc); + dragging = polygon; + draggingOffsetX = loc.x - polygon.x; + draggingOffsetY = loc.y - polygon.y; + return; + } + }); + } + else { + startDragging(loc); + dragging = true; + } +}; + +canvas.onmousemove = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + e.preventDefault(); // prevent selections + + if (editing && dragging) { + dragging.x = loc.x - draggingOffsetX; + dragging.y = loc.y - draggingOffsetY; + + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + drawPolygons(); + } + else { + if (dragging) { + restoreDrawingSurface(); + updateRubberband(loc, sides, startAngle); + + if (guidewires) { + drawGuidewires(mousedown.x, mousedown.y); + } + } + } +}; + +canvas.onmouseup = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + dragging = false; + + if (editing) { + } + else { + restoreDrawingSurface(); + updateRubberband(loc); + } +}; + +eraseAllButton.onclick = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + saveDrawingSurface(); +}; + +strokeStyleSelect.onchange = function (e) { + context.strokeStyle = strokeStyleSelect.value; +}; + +fillStyleSelect.onchange = function (e) { + context.fillStyle = fillStyleSelect.value; +}; + +editCheckbox.onchange = function (e) { + if (editCheckbox.checked) { + startEditing(); + } + else { + stopEditing(); + } +}; + +// Initialization..................................................... + +context.strokeStyle = strokeStyleSelect.value; +context.fillStyle = fillStyleSelect.value; + +drawGrid('lightgray', 10, 10); + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(0, 0, 0, 0.4)'; + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; diff --git a/canvas/ch02/example-2.29/example.html b/canvas/ch02/example-2.29/example.html new file mode 100644 index 0000000..131910b --- /dev/null +++ b/canvas/ch02/example-2.29/example.html @@ -0,0 +1,116 @@ + + + + + + Drawing Bezier Curves + + + + + + + Canvas not supported + + +
+ Stroke color: + Guidewires: + +
+ +
+

Drag the curve end- and control points to + change the shape of the curve.

+ +

When you are done dragging end- and control points, + click outside of the points to finalize the curve.

+ + + +
+ + + + diff --git a/canvas/ch02/example-2.29/example.js b/canvas/ch02/example-2.29/example.js new file mode 100644 index 0000000..a84e0aa --- /dev/null +++ b/canvas/ch02/example-2.29/example.js @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + eraseAllButton = document.getElementById('eraseAllButton'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + guidewireCheckbox = document.getElementById('guidewireCheckbox'), + instructions = document.getElementById('instructions'), + instructionsOkayButton = document.getElementById('instructionsOkayButton'), + instructionsNoMoreButton = document.getElementById('instructionsNoMoreButton'), + + showInstructions = true, + + GRID_STROKE_STYLE = 'lightblue', + GRID_SPACING = 10, + + CONTROL_POINT_RADIUS = 5, + CONTROL_POINT_STROKE_STYLE = 'blue', + CONTROL_POINT_FILL_STYLE = 'rgba(255, 255, 0, 0.5)', + + END_POINT_STROKE_STYLE = 'navy', + END_POINT_FILL_STYLE = 'rgba(0, 255, 0, 0.5)', + + GUIDEWIRE_STROKE_STYLE = 'rgba(0,0,230,0.4)', + + drawingImageData, // Image data stored on mouse down events + + mousedown = {}, // Cursor location for last mouse down event + rubberbandRect = {}, // Constantly updated for mouse move events + + dragging = false, // If true, user is dragging the cursor + draggingPoint = false, // End- or control-point the user is dragging + + endPoints = [ {}, {} ], // end point locations (x, y) + controlPoints = [ {}, {} ], // control point locations (x, y) + editing = false, // If true, user is editing the curve + + guidewires = guidewireCheckbox.checked; + +// Functions.......................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.lineWidth = 0.5; + context.clearRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Save and restore drawing surface................................... + +function saveDrawingSurface() { + drawingImageData = context.getImageData(0, 0, + canvas.width, canvas.height); +} + +function restoreDrawingSurface() { + context.putImageData(drawingImageData, 0, 0); +} + +// Rubberbands........................................................ + +function updateRubberbandRectangle(loc) { + rubberbandRect.width = Math.abs(loc.x - mousedown.x); + rubberbandRect.height = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x; + else rubberbandRect.left = loc.x; + + if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y; + else rubberbandRect.top = loc.y; +} + +function drawBezierCurve() { + context.beginPath(); + context.moveTo(endPoints[0].x, endPoints[0].y); + context.bezierCurveTo(controlPoints[0].x, controlPoints[0].y, + controlPoints[1].x, controlPoints[1].y, + endPoints[1].x, endPoints[1].y); + context.stroke(); +} + +function updateEndAndControlPoints() { + endPoints[0].x = rubberbandRect.left; + endPoints[0].y = rubberbandRect.top; + + endPoints[1].x = rubberbandRect.left + rubberbandRect.width; + endPoints[1].y = rubberbandRect.top + rubberbandRect.height + + controlPoints[0].x = rubberbandRect.left; + controlPoints[0].y = rubberbandRect.top + rubberbandRect.height + + controlPoints[1].x = rubberbandRect.left + rubberbandRect.width; + controlPoints[1].y = rubberbandRect.top; +} + +function drawRubberbandShape(loc) { + updateEndAndControlPoints(); + drawBezierCurve(); +} + +function updateRubberband(loc) { + updateRubberbandRectangle(loc); + drawRubberbandShape(loc); +} + +// Guidewires......................................................... + +function drawHorizontalGuidewire (y) { + context.beginPath(); + context.moveTo(0, y + 0.5); + context.lineTo(context.canvas.width, y + 0.5); + context.stroke(); +} + +function drawVerticalGuidewire (x) { + context.beginPath(); + context.moveTo(x + 0.5, 0); + context.lineTo(x + 0.5, context.canvas.height); + context.stroke(); +} + +function drawGuidewires(x, y) { + context.save(); + context.strokeStyle = GUIDEWIRE_STROKE_STYLE; + context.lineWidth = 0.5; + drawVerticalGuidewire(x); + drawHorizontalGuidewire(y); + context.restore(); +} + +// End points and control points...................................... + +function drawControlPoint(index) { + context.beginPath(); + context.arc(controlPoints[index].x, controlPoints[index].y, + CONTROL_POINT_RADIUS, 0, Math.PI*2, false); + context.stroke(); + context.fill(); +} + +function drawControlPoints() { + context.save(); + context.strokeStyle = CONTROL_POINT_STROKE_STYLE; + context.fillStyle = CONTROL_POINT_FILL_STYLE; + + drawControlPoint(0); + drawControlPoint(1); + + context.stroke(); + context.fill(); + context.restore(); +} + +function drawEndPoint(index) { + context.beginPath(); + context.arc(endPoints[index].x, endPoints[index].y, + CONTROL_POINT_RADIUS, 0, Math.PI*2, false); + context.stroke(); + context.fill(); +} + +function drawEndPoints() { + context.save(); + context.strokeStyle = END_POINT_STROKE_STYLE; + context.fillStyle = END_POINT_FILL_STYLE; + + drawEndPoint(0); + drawEndPoint(1); + + context.stroke(); + context.fill(); + context.restore(); +} + +function drawControlAndEndPoints() { + drawControlPoints(); + drawEndPoints(); +} + +function cursorInEndPoint(loc) { + var pt; + + endPoints.forEach( function(point) { + context.beginPath(); + context.arc(point.x, point.y, + CONTROL_POINT_RADIUS, 0, Math.PI*2, false); + + if (context.isPointInPath(loc.x, loc.y)) { + pt = point; + } + }); + + return pt; +} + +function cursorInControlPoint(loc) { + var pt; + + controlPoints.forEach( function(point) { + context.beginPath(); + context.arc(point.x, point.y, + CONTROL_POINT_RADIUS, 0, Math.PI*2, false); + + if (context.isPointInPath(loc.x, loc.y)) { + pt = point; + } + }); + + return pt; +} + +function updateDraggingPoint(loc) { + draggingPoint.x = loc.x; + draggingPoint.y = loc.y; +} + +// Canvas event handlers.............................................. + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + e.preventDefault(); // prevent cursor change + + if (!editing) { + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; + updateRubberbandRectangle(loc); + dragging = true; + } + else { + draggingPoint = cursorInControlPoint(loc); + + if (!draggingPoint) { + draggingPoint = cursorInEndPoint(loc); + } + } +}; + +canvas.onmousemove = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + if (dragging || draggingPoint) { + e.preventDefault(); // prevent selections + restoreDrawingSurface(); + + if(guidewires) { + drawGuidewires(loc.x, loc.y); + } + } + + if (dragging) { + updateRubberband(loc); + drawControlAndEndPoints(); + } + else if (draggingPoint) { + updateDraggingPoint(loc); + drawControlAndEndPoints(); + drawBezierCurve(); + } +}; + +canvas.onmouseup = function (e) { + loc = windowToCanvas(e.clientX, e.clientY); + + restoreDrawingSurface(); + + if (!editing) { + updateRubberband(loc); + drawControlAndEndPoints(); + dragging = false; + editing = true; + if (showInstructions) { + instructions.style.display = 'inline'; + } + } + else { + if (draggingPoint) drawControlAndEndPoints(); + else editing = false; + + drawBezierCurve(); + draggingPoint = undefined; + } +}; + +// Control event handlers............................................. + +eraseAllButton.onclick = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid(GRID_STROKE_STYLE, GRID_SPACING, GRID_SPACING); + + saveDrawingSurface(); + + editing = false; + dragging = false; + draggingPoint = undefined; +}; + +strokeStyleSelect.onchange = function (e) { + context.strokeStyle = strokeStyleSelect.value; +}; + +guidewireCheckbox.onchange = function (e) { + guidewires = guidewireCheckbox.checked; +}; + +// Instructions event handlers........................................ + +instructionsOkayButton.onclick = function (e) { + instructions.style.display = 'none'; +}; + +instructionsNoMoreButton.onclick = function (e) { + instructions.style.display = 'none'; + showInstructions = false; +}; + +// Initialization..................................................... + +context.strokeStyle = strokeStyleSelect.value; +drawGrid(GRID_STROKE_STYLE, GRID_SPACING, GRID_SPACING); diff --git a/canvas/ch02/example-2.3/example.html b/canvas/ch02/example-2.3/example.html new file mode 100644 index 0000000..43efd31 --- /dev/null +++ b/canvas/ch02/example-2.3/example.html @@ -0,0 +1,51 @@ + + + + + Linear Gradients + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.3/example.js b/canvas/ch02/example-2.3/example.js new file mode 100644 index 0000000..1eb7964 --- /dev/null +++ b/canvas/ch02/example-2.3/example.js @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + gradient = context.createLinearGradient( + 0, 0, 0, canvas.height/2); + +gradient.addColorStop(0, 'blue'); +gradient.addColorStop(0.25, 'white'); +gradient.addColorStop(0.5, 'purple'); +gradient.addColorStop(0.75, 'red'); +gradient.addColorStop(1, 'yellow'); + +context.fillStyle = gradient; +context.rect(0, 0, canvas.width, canvas.height); +context.fill(); diff --git a/canvas/ch02/example-2.31/example.html b/canvas/ch02/example-2.31/example.html new file mode 100644 index 0000000..1610047 --- /dev/null +++ b/canvas/ch02/example-2.31/example.html @@ -0,0 +1,127 @@ + + + + + + Rotating Polygons + + + + + + + Canvas not supported + + +
+ Stroke color: + + Fill color: + + Sides: + + + Start angle: + + Fill + +
+ +
+ Edit: +
+ + + + + diff --git a/canvas/ch02/example-2.31/example.js b/canvas/ch02/example-2.31/example.js new file mode 100644 index 0000000..3396215 --- /dev/null +++ b/canvas/ch02/example-2.31/example.js @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + eraseAllButton = document.getElementById('eraseAllButton'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + startAngleSelect = document.getElementById('startAngleSelect'), + + fillStyleSelect = document.getElementById('fillStyleSelect'), + fillCheckbox = document.getElementById('fillCheckbox'), + editCheckbox = document.getElementById('editCheckbox'), + + sidesSelect = document.getElementById('sidesSelect'), + + CENTROID_RADIUS = 10, + CENTROID_STROKE_STYLE = 'rgba(0, 0, 0, 0.8)', + CENTROID_FILL_STYLE ='rgba(255, 255, 255, 0.2)', + CENTROID_SHADOW_COLOR = 'rgba(255, 255, 255, 0.4)', + + DEGREE_RING_MARGIN = 35, + TRACKING_RING_MARGIN = 55, + DEGREE_ANNOTATIONS_FILL_STYLE = 'rgba(0, 0, 230, 0.8)', + DEGREE_ANNOTATIONS_TEXT_SIZE = 11, + DEGREE_OUTER_RING_MARGIN = DEGREE_RING_MARGIN, + TICK_WIDTH = 10, + TICK_LONG_STROKE_STYLE = 'rgba(100, 140, 230, 0.9)', + TICK_SHORT_STROKE_STYLE = 'rgba(100, 140, 230, 0.7)', + + TRACKING_RING_STROKING_STYLE = 'rgba(100, 140, 230, 0.3)', + drawingSurfaceImageData, + + mousedown = {}, + rubberbandRect = {}, + + dragging = false, + draggingOffsetX, + draggingOffsetY, + + sides = 8, + startAngle = 0, + + guidewires = true, + + editing = false, + rotatingLockEngaged = false, + rotatingLockAngle, + polygonRotating, + + polygons = []; + +// Functions..................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Save and restore drawing surface.............................. + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +function restoreDrawingSurface() { + context.putImageData(drawingSurfaceImageData, 0, 0); +} + + +// Rubberbands................................................... + +function updateRubberbandRectangle(loc) { + rubberbandRect.width = Math.abs(loc.x - mousedown.x); + rubberbandRect.height = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x; + else rubberbandRect.left = loc.x; + + if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y; + else rubberbandRect.top = loc.y; +} + +function drawRubberbandShape(loc, sides, startAngle) { + var polygon = new Polygon(mousedown.x, mousedown.y, + rubberbandRect.width, + parseInt(sidesSelect.value), + (Math.PI / 180) * parseInt(startAngleSelect.value), + context.strokeStyle, + context.fillStyle, + fillCheckbox.checked); + drawPolygon(polygon); + + if (!dragging) { + polygons.push(polygon); + } +} + +function updateRubberband(loc, sides, startAngle) { + updateRubberbandRectangle(loc); + drawRubberbandShape(loc, sides, startAngle); +} + +// Guidewires.................................................... + +function drawHorizontalLine (y) { + context.beginPath(); + context.moveTo(0,y+0.5); + context.lineTo(context.canvas.width,y+0.5); + context.stroke(); +} + +function drawVerticalLine (x) { + context.beginPath(); + context.moveTo(x+0.5,0); + context.lineTo(x+0.5,context.canvas.height); + context.stroke(); +} + +function drawGuidewires(x, y) { + context.save(); + context.strokeStyle = 'rgba(0,0,230,0.4)'; + context.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + context.restore(); +} + +// Drawing functions............................................. + +function drawPolygons() { + polygons.forEach( function (polygon) { + polygon.stroke(context); + if (polygon.filled) { + polygon.fill(context); + } + }); +} + +function drawCentroid(polygon) { + context.beginPath(); + context.save(); + context.strokeStyle = CENTROID_STROKE_STYLE; + context.fillStyle = CENTROID_FILL_STYLE; + context.shadowColor = CENTROID_SHADOW_COLOR; + context.arc(polygon.x, polygon.y, CENTROID_RADIUS, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + context.restore(); +} + +function drawCentroidGuidewire(loc, polygon) { + var angle = Math.atan( (loc.y - polygon.y) / (loc.x - polygon.x) ), + radius, endpt; + + radius = polygon.radius + TRACKING_RING_MARGIN; + angle = angle - rotatingLockAngle; + + if (loc.x >= polygon.x) { + endpt = { x: polygon.x + radius * Math.cos(angle), + y: polygon.y + radius * Math.sin(angle) + }; + } + else { + endpt = { x: polygon.x - radius * Math.cos(angle), + y: polygon.y - radius * Math.sin(angle) + }; + } + + context.save(); + context.beginPath(); + context.moveTo(polygon.x, polygon.y); + context.lineTo(endpt.x, endpt.y); + context.stroke(); + + context.beginPath(); + context.arc(endpt.x, endpt.y, 5, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + + context.restore(); +} + +function drawDegreeOuterDial(polygon) { + context.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + context.arc(polygon.x, polygon.y, + polygon.radius + DEGREE_OUTER_RING_MARGIN, + 0, Math.PI*2, true); +} + +function drawDegreeAnnotations(polygon) { + var radius = polygon.radius + DEGREE_RING_MARGIN; + + context.save(); + context.fillStyle = DEGREE_ANNOTATIONS_FILL_STYLE; + context.font = DEGREE_ANNOTATIONS_TEXT_SIZE + 'px Helvetica'; + + for (var angle=0; angle < 2*Math.PI; angle += Math.PI/8) { + context.beginPath(); + context.fillText((angle * 180 / Math.PI).toFixed(0), + polygon.x + Math.cos(angle) * (radius - TICK_WIDTH*2), + polygon.y + Math.sin(angle) * (radius - TICK_WIDTH*2)); + } + context.restore(); +} + +function drawDegreeDialTicks(polygon) { + var radius = polygon.radius + DEGREE_RING_MARGIN, + ANGLE_MAX = 2*Math.PI, + ANGLE_DELTA = Math.PI/64; + + context.save(); + + for (var angle = 0, cnt = 0; angle < ANGLE_MAX; angle += ANGLE_DELTA, ++cnt) { + context.beginPath(); + + if (cnt % 4 === 0) { + context.moveTo(polygon.x + Math.cos(angle) * (radius - TICK_WIDTH), + polygon.y + Math.sin(angle) * (radius - TICK_WIDTH)); + context.lineTo(polygon.x + Math.cos(angle) * (radius), + polygon.y + Math.sin(angle) * (radius)); + context.strokeStyle = TICK_LONG_STROKE_STYLE; + context.stroke(); + } + else { + context.moveTo(polygon.x + Math.cos(angle) * (radius - TICK_WIDTH/2), + polygon.y + Math.sin(angle) * (radius - TICK_WIDTH/2)); + context.lineTo(polygon.x + Math.cos(angle) * (radius), + polygon.y + Math.sin(angle) * (radius)); + context.strokeStyle = TICK_SHORT_STROKE_STYLE; + context.stroke(); + } + + context.restore(); + } +} + +function drawDegreeTickDial(polygon) { + context.save(); + context.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + context.beginPath(); + context.arc(polygon.x, polygon.y, polygon.radius + DEGREE_RING_MARGIN - TICK_WIDTH, 0, Math.PI*2, false); + context.stroke(); + context.restore(); +} + +function drawTrackingDial(polygon) { + context.save(); + context.shadowColor = 'rgba(0, 0, 0, 0.7)'; + context.shadowOffsetX = 3, + context.shadowOffsetY = 3, + context.shadowBlur = 6, + context.strokeStyle = TRACKING_RING_STROKING_STYLE; + context.beginPath(); + context.arc(polygon.x, polygon.y, polygon.radius + + TRACKING_RING_MARGIN, 0, Math.PI*2, false); + context.stroke(); + context.restore(); +} + +function drawRotationAnnotations(loc) { + drawCentroid(polygonRotating); + drawCentroidGuidewire(loc, polygonRotating); + + drawTrackingDial(polygonRotating); + drawDegreeOuterDial(polygonRotating); + context.fillStyle = 'rgba(100, 140, 230, 0.1)'; + context.fill(); + + context.beginPath(); + drawDegreeOuterDial(polygonRotating); + context.stroke(); + + drawDegreeDialTicks(polygonRotating); + drawDegreeTickDial(polygonRotating); + drawDegreeAnnotations(polygonRotating); +} + +function redraw() { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + drawPolygons(); +} + +// Polygons...................................................... + +function drawPolygon(polygon, angle) { + var tx = polygon.x, + ty = polygon.y; + + context.save(); + + context.translate(tx, ty); + + if (angle) { + context.rotate(angle); + } + + polygon.x = 0; + polygon.y = 0; + + polygon.createPath(context); + context.stroke(); + + if (fillCheckbox.checked) { + context.fill(); + } + + context.restore(); + + polygon.x = tx; + polygon.y = ty; +} + +function getSelectedPolygon(loc) { + for (var i=0; i < polygons.length; ++i) { + var polygon = polygons[i]; + + polygon.createPath(context); + if (context.isPointInPath(loc.x, loc.y)) { + startDragging(loc); + draggingOffsetX = loc.x - polygon.x; + draggingOffsetY = loc.y - polygon.y; + return polygon; + } + } + return undefined; +} + +function stopRotatingPolygon(loc) { + angle = Math.atan((loc.y - polygonRotating.y) / + (loc.x - polygonRotating.x)) + - rotatingLockAngle; + + polygonRotating.startAngle += angle; + + polygonRotating = undefined; + rotatingLockEngaged = false; + rotatingLockAngle = 0; +} + +function startDragging(loc) { + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; +} + +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e), + angle, + radius, + trackingRadius; + + e.preventDefault(); // prevent cursor change + + if (editing) { + if (polygonRotating) { + stopRotatingPolygon(loc); + redraw(); + } + + polygonRotating = getSelectedPolygon(loc); + + if (polygonRotating) { + drawRotationAnnotations(loc); + + if (!rotatingLockEngaged) { + rotatingLockEngaged = true; + rotatingLockAngle = Math.atan((loc.y - polygonRotating.y) / + (loc.x - polygonRotating.x)); + } + } + } + else { + startDragging(loc); + dragging = true; + } +}; + +canvas.onmousemove = function (e) { + var loc = windowToCanvas(e), + radius = Math.sqrt(Math.pow(loc.x - dragging.x, 2) + + Math.pow(loc.y - dragging.y, 2)), + angle; + + + e.preventDefault(); // prevent selections + + if (rotatingLockEngaged) { + angle = Math.atan((loc.y - polygonRotating.y) / + (loc.x - polygonRotating.x)) + - rotatingLockAngle; + + redraw(); + + drawPolygon(polygonRotating, angle); + drawRotationAnnotations(loc); + } + else if (dragging) { + restoreDrawingSurface(); + updateRubberband(loc, sides, startAngle); + + if (guidewires) { + drawGuidewires(mousedown.x, mousedown.y); + } + } +}; + +canvas.onmouseup = function (e) { + var loc = windowToCanvas(e); + + dragging = false; + + if (!editing) { + restoreDrawingSurface(); + updateRubberband(loc); + } +}; + +eraseAllButton.onclick = function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + saveDrawingSurface(); +}; + +strokeStyleSelect.onchange = function (e) { + context.strokeStyle = strokeStyleSelect.value; +}; + +fillStyleSelect.onchange = function (e) { + context.fillStyle = fillStyleSelect.value; +}; + +function startEditing() { + canvas.style.cursor = 'pointer'; + editing = true; +} + +function stopEditing() { + canvas.style.cursor = 'crosshair'; + editing = false; + polygonRotating = undefined; + rotatingLockEngaged = false; + rotatingLockAngle = 0; + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + drawPolygons(); +} + +editCheckbox.onchange = function (e) { + if (editCheckbox.checked) { + startEditing(); + } + else { + stopEditing(); + } +}; + +// Initialization................................................ + +context.strokeStyle = strokeStyleSelect.value; +context.fillStyle = fillStyleSelect.value; + +drawGrid('lightgray', 10, 10); + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(0, 0, 0, 0.4)'; + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; + +context.textAlign = 'center'; +context.textBaseline = 'middle'; + diff --git a/canvas/ch02/example-2.32/example.html b/canvas/ch02/example-2.32/example.html new file mode 100644 index 0000000..1013ba6 --- /dev/null +++ b/canvas/ch02/example-2.32/example.html @@ -0,0 +1,73 @@ + + + + + + Canvas Composite Operations + + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.32/example.js b/canvas/ch02/example-2.32/example.js new file mode 100644 index 0000000..7e98fb6 --- /dev/null +++ b/canvas/ch02/example-2.32/example.js @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'), + selectElement = document.getElementById('compositingSelect'); + +// Functions..................................................... + +function drawText() { + context.save(); + + context.shadowColor = 'rgba(100, 100, 150, 0.8)'; + context.shadowOffsetX = 5; + context.shadowOffsetY = 5; + context.shadowBlur = 10; + + context.fillStyle = 'cornflowerblue'; + context.fillText('HTML5', 20, 250); + + context.strokeStyle = 'yellow'; + context.strokeText('HTML5', 20, 250); + + context.restore(); +} + +// Event handlers............................................... + +function windowToCanvas(canvas, x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +context.canvas.onmousemove = function(e) { + var loc = windowToCanvas(context.canvas, e.clientX, e.clientY); + context.clearRect(0, 0, context.canvas.width, context.canvas.height); + drawText(); + + context.save(); + + context.globalCompositeOperation = selectElement.value; + context.beginPath(); + context.arc(loc.x, loc.y, 100, 0, Math.PI*2, false); + context.fillStyle = 'orange'; + context.stroke(); + context.fill(); + + context.restore(); +} + +// Initialization................................................ + +selectElement.selectedIndex = 3; +context.lineWidth = 0.5; +context.font = '128pt Comic-sans'; +drawText(); diff --git a/canvas/ch02/example-2.34/example.html b/canvas/ch02/example-2.34/example.html new file mode 100644 index 0000000..ee1b0fa --- /dev/null +++ b/canvas/ch02/example-2.34/example.html @@ -0,0 +1,111 @@ + + + + + + Erasing with the Clipping Region + + + + + + + Canvas not supported + + +
+ Stroke color: + + Fill color: + + Draw + Erase + + Eraser: + + Eraser width: +
+ + + + diff --git a/canvas/ch02/example-2.34/example.js b/canvas/ch02/example-2.34/example.js new file mode 100644 index 0000000..e3863a6 --- /dev/null +++ b/canvas/ch02/example-2.34/example.js @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + drawRadio = document.getElementById('drawRadio'), + eraserRadio = document.getElementById('eraserRadio'), + eraserShapeSelect = document.getElementById('eraserShapeSelect'), + eraserWidthSelect = document.getElementById('eraserWidthSelect'), + + ERASER_LINE_WIDTH = 1, + + ERASER_SHADOW_COLOR = 'rgb(0,0,0)', + ERASER_SHADOW_STYLE = 'blue', + ERASER_STROKE_STYLE = 'rgb(0,0,255)', + ERASER_SHADOW_OFFSET = -5, + ERASER_SHADOW_BLUR = 20, + + GRID_HORIZONTAL_SPACING = 10, + GRID_VERTICAL_SPACING = 10, + GRID_LINE_COLOR = 'lightblue', + drawingSurfaceImageData, + + lastX, + lastY, + mousedown = {}, + rubberbandRect = {}, + dragging = false, + guidewires = true; + +// General-purpose functions..................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Save and restore drawing surface.............................. + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +function restoreDrawingSurface() { + context.putImageData(drawingSurfaceImageData, 0, 0); +} + +// Rubberbands................................................... + +function updateRubberbandRectangle(loc) { + rubberbandRect.width = Math.abs(loc.x - mousedown.x); + rubberbandRect.height = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x; + else rubberbandRect.left = loc.x; + + if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y; + else rubberbandRect.top = loc.y; +} + +function drawRubberbandShape(loc) { + var angle = Math.atan(rubberbandRect.height/rubberbandRect.width), + radius = rubberbandRect.height / Math.sin(angle); + + if (mousedown.y === loc.y) { + radius = Math.abs(loc.x - mousedown.x); + } + + context.beginPath(); + context.arc(mousedown.x, mousedown.y, radius, 0, Math.PI*2, false); + context.stroke(); + context.fill(); +} + +function updateRubberband(loc) { + updateRubberbandRectangle(loc); + drawRubberbandShape(loc); +} + +// Guidewires.................................................... + +function drawHorizontalLine (y) { + context.beginPath(); + context.moveTo(0,y+0.5); + context.lineTo(context.canvas.width,y+0.5); + context.stroke(); +} + +function drawVerticalLine (x) { + context.beginPath(); + context.moveTo(x+0.5,0); + context.lineTo(x+0.5,context.canvas.height); + context.stroke(); +} + +function drawGuidewires(x, y) { + context.save(); + context.strokeStyle = 'rgba(0,0,230,0.4)'; + context.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + context.restore(); +} + +// Eraser........................................................ + +function setDrawPathForEraser(loc) { + var eraserWidth = parseFloat(eraserWidthSelect.value); + + context.beginPath(); + + if (eraserShapeSelect.value === 'circle') { + context.arc(loc.x, loc.y, + eraserWidth/2, + 0, Math.PI*2, false); + } + else { + context.rect(loc.x - eraserWidth/2, + loc.y - eraserWidth/2, + eraserWidth, eraserWidth); + } + context.clip(); +} + +function setErasePathForEraser() { + var eraserWidth = parseFloat(eraserWidthSelect.value); + + context.beginPath(); + + if (eraserShapeSelect.value === 'circle') { + context.arc(lastX, lastY, + eraserWidth/2 + ERASER_LINE_WIDTH, + 0, Math.PI*2, false); + } + else { + context.rect(lastX - eraserWidth/2 - ERASER_LINE_WIDTH, + lastY - eraserWidth/2 - ERASER_LINE_WIDTH, + eraserWidth + ERASER_LINE_WIDTH*2, + eraserWidth + ERASER_LINE_WIDTH*2); + } + context.clip(); +} + +function setEraserAttributes() { + context.lineWidth = ERASER_LINE_WIDTH; + context.shadowColor = ERASER_SHADOW_STYLE; + context.shadowOffsetX = ERASER_SHADOW_OFFSET; + context.shadowOffsetY = ERASER_SHADOW_OFFSET; + context.shadowBlur = ERASER_SHADOW_BLUR; + context.strokeStyle = ERASER_STROKE_STYLE; +} + +function eraseLast() { + context.save(); + + setErasePathForEraser(); + drawGrid(GRID_LINE_COLOR, + GRID_HORIZONTAL_SPACING, + GRID_VERTICAL_SPACING); + + context.restore(); +} + +function drawEraser(loc) { + context.save(); + + setEraserAttributes(); + setDrawPathForEraser(loc); + context.stroke(); + + context.restore(); +} + +// Canvas event handlers......................................... + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY); + + e.preventDefault(); // prevent cursor change + + if (drawRadio.checked) { + saveDrawingSurface(); + } + + mousedown.x = loc.x; + mousedown.y = loc.y; + + lastX = loc.x; + lastY = loc.y; + + dragging = true; +}; + +canvas.onmousemove = function (e) { + var loc; + + if (dragging) { + e.preventDefault(); // prevent selections + + loc = windowToCanvas(e.clientX, e.clientY); + + if (drawRadio.checked) { + restoreDrawingSurface(); + updateRubberband(loc); + + if(guidewires) { + drawGuidewires(loc.x, loc.y); + } + } + else { + eraseLast(); + drawEraser(loc); + } + lastX = loc.x; + lastY = loc.y; + } +}; + +canvas.onmouseup = function (e) { + loc = windowToCanvas(e.clientX, e.clientY); + + if (drawRadio.checked) { + restoreDrawingSurface(); + updateRubberband(loc); + } + + if (eraserRadio.checked) { + eraseLast(); + } + + dragging = false; +}; + +// Controls event handlers....................................... + +strokeStyleSelect.onchange = function (e) { + context.strokeStyle = strokeStyleSelect.value; +}; + +fillStyleSelect.onchange = function (e) { + context.fillStyle = fillStyleSelect.value; +}; + +// Initialization................................................ + +context.strokeStyle = strokeStyleSelect.value; +context.fillStyle = fillStyleSelect.value; +drawGrid(GRID_LINE_COLOR, + GRID_HORIZONTAL_SPACING, + GRID_VERTICAL_SPACING); + diff --git a/canvas/ch02/example-2.35/example.html b/canvas/ch02/example-2.35/example.html new file mode 100644 index 0000000..01c10e9 --- /dev/null +++ b/canvas/ch02/example-2.35/example.html @@ -0,0 +1,57 @@ + + + + + + Telescoping with the Canvas clip region + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.35/example.js b/canvas/ch02/example-2.35/example.js new file mode 100644 index 0000000..46cdc1a --- /dev/null +++ b/canvas/ch02/example-2.35/example.js @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'); + +// Functions.......................................................... + +function drawText() { + context.save(); + context.shadowColor = 'rgba(100, 100, 150, 0.8)'; + context.shadowOffsetX = 5; + context.shadowOffsetY = 5; + context.shadowBlur = 10; + + context.fillStyle = 'cornflowerblue'; + context.fillText('HTML5', 20, 250); + context.strokeStyle = 'yellow'; + context.strokeText('HTML5', 20, 250); + context.restore(); +} + +function setClippingRegion(radius) { + context.beginPath(); + context.arc(canvas.width/2, canvas.height/2, + radius, 0, Math.PI*2, false); + context.clip(); +} + +function fillCanvas(color) { + context.fillStyle = color; + context.fillRect(0, 0, canvas.width, canvas.height); +} + +function endAnimation(loop) { + clearInterval(loop); + + setTimeout( function (e) { + context.clearRect(0, 0, canvas.width, canvas.height); + drawText(); + }, 1000); +} + +function drawAnimationFrame(radius) { + setClippingRegion(radius); + fillCanvas('lightgray'); + drawText(); +} + +function animate() { + var radius = canvas.width/2, + loop; + + loop = window.setInterval(function() { + radius -= canvas.width/100; + + fillCanvas('charcoal'); + + if (radius > 0) { + context.save(); + drawAnimationFrame(radius); + context.restore(); + } + else { + endAnimation(loop); + } + }, 16); +}; + +// Event handlers.................................................... + +canvas.onmousedown = function (e) { + animate(); +}; + +// Initialization..................................................... + +context.lineWidth = 0.5; +context.font = '128pt Comic-sans'; +drawText(); diff --git a/canvas/ch02/example-2.4/example.html b/canvas/ch02/example-2.4/example.html new file mode 100644 index 0000000..f2b67fe --- /dev/null +++ b/canvas/ch02/example-2.4/example.html @@ -0,0 +1,53 @@ + + + + + Radial Gradients + + + + + +
+ + Canvas not supported + + + +
+ + diff --git a/canvas/ch02/example-2.4/example.js b/canvas/ch02/example-2.4/example.js new file mode 100644 index 0000000..3f23995 --- /dev/null +++ b/canvas/ch02/example-2.4/example.js @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + gradient = context.createRadialGradient( + canvas.width/2, canvas.height, 10, + canvas.width/2, 0, 100); + +gradient.addColorStop(0, 'blue'); +gradient.addColorStop(0.25, 'white'); +gradient.addColorStop(0.5, 'purple'); +gradient.addColorStop(0.75, 'red'); +gradient.addColorStop(1, 'yellow'); + +context.fillStyle = gradient; +context.rect(0, 0, canvas.width, canvas.height); +context.fill(); diff --git a/canvas/ch02/example-2.5/example.html b/canvas/ch02/example-2.5/example.html new file mode 100644 index 0000000..4e06cc9 --- /dev/null +++ b/canvas/ch02/example-2.5/example.html @@ -0,0 +1,62 @@ + + + + + Patterns + + + + + +
+ repeat + repeat-x + repeat-y + no repeat +
+ + + Canvas not supported + + + + + diff --git a/canvas/ch02/example-2.5/example.js b/canvas/ch02/example-2.5/example.js new file mode 100644 index 0000000..e9e32a3 --- /dev/null +++ b/canvas/ch02/example-2.5/example.js @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + repeatRadio = document.getElementById('repeatRadio'), + noRepeatRadio = document.getElementById('noRepeatRadio'), + repeatXRadio = document.getElementById('repeatXRadio'), + repeatYRadio = document.getElementById('repeatYRadio'), + image = new Image(); + +function fillCanvasWithPattern(repeatString) { + var pattern = context.createPattern(image, repeatString); + context.clearRect(0, 0, canvas.width, canvas.height); + context.fillStyle = pattern; + context.fillRect(0, 0, canvas.width, canvas.height); + context.fill(); +}; + +repeatRadio.onclick = function (e) { + fillCanvasWithPattern('repeat'); +}; + +repeatXRadio.onclick = function (e) { + fillCanvasWithPattern('repeat-x'); +}; + +repeatYRadio.onclick = function (e) { + fillCanvasWithPattern('repeat-y'); +}; + +noRepeatRadio.onclick = function (e) { + fillCanvasWithPattern('no-repeat'); +}; + +image.src = 'redball.png'; +image.onload = function (e) { + fillCanvasWithPattern('repeat'); +}; + diff --git a/canvas/ch02/example-2.5/redball.png b/canvas/ch02/example-2.5/redball.png new file mode 100644 index 0000000..4274f3e Binary files /dev/null and b/canvas/ch02/example-2.5/redball.png differ diff --git a/canvas/ch02/example-2.7/example.html b/canvas/ch02/example-2.7/example.html new file mode 100644 index 0000000..5839781 --- /dev/null +++ b/canvas/ch02/example-2.7/example.html @@ -0,0 +1,248 @@ + + + + + + Paint + + + + +
+ Stroke: + + Fill: + + Line width: + + + + Drag the image to your desktop (or Right-click to save to disk) +
+ + + + + Canvas not supported + + + + Canvas not supported + + +
+ +
+

+ The yellow circle is a control point for the curve. Drag that + circle around to change the shape of the curve. +

+ + + +
+ + + + + diff --git a/canvas/ch02/example-2.7/example.js b/canvas/ch02/example-2.7/example.js new file mode 100644 index 0000000..04bde21 --- /dev/null +++ b/canvas/ch02/example-2.7/example.js @@ -0,0 +1,1101 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var iconCanvas = document.getElementById('iconCanvas'), + drawingCanvas = document.getElementById('drawingCanvas'), + drawingContext = drawingCanvas.getContext('2d'), + backgroundContext = document.createElement('canvas').getContext('2d'), + iconContext = iconCanvas.getContext('2d'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + lineWidthSelect = document.getElementById('lineWidthSelect'), + eraseAllButton = document.getElementById('eraseAllButton'), + snapshotButton = document.getElementById('snapshotButton'), + controls = document.getElementById('controls'), + curveInstructions = document.getElementById('curveInstructions'), + curveInstructionsOkayButton = document.getElementById('curveInstructionsOkayButton'), + curveInstructionsNoMoreButton = document.getElementById('curveInstructionsNoMoreButton'), + + showCurveInstructions = true, + + drawingSurfaceImageData, + rubberbandW, + rubberbandH, + rubberbandUlhc = {}, + + dragging = false, + mousedown = {}, + lastRect = {}, + lastX, lastY, + + controlPoint = {}, + editingCurve = false, + draggingControlPoint = false, + curveStart = {}, + curveEnd = {}, + + doFill = false, + selectedRect = null, + selectedFunction, + + editingText = false, + currentText, + + CONTROL_POINT_RADIUS = 20, + CONTROL_POINT_FILL_STYLE = 'rgba(255,255,0,0.5)', + CONTROL_POINT_STROKE_STYLE = 'rgba(0, 0, 255, 0.8)', + + RUBBERBAND_LINE_WIDTH = 1, + RUBBERBAND_STROKE_STYLE = 'green', + + GRID_HORIZONTAL_SPACING = 10, + GRID_VERTICAL_SPACING = 10, + GRID_LINE_COLOR = 'rgb(0, 0, 200)', + + ERASER_ICON_GRID_COLOR = 'rgb(0, 0, 200)', + ERASER_ICON_CIRCLE_COLOR = 'rgba(100, 140, 200, 0.5)', + ERASER_ICON_RADIUS = 20, + + SLINKY_LINE_WIDTH = 1, + SLINKY_SHADOW_STYLE = 'rgba(0,0,0,0.2)', + SLINKY_SHADOW_OFFSET = -5, + SLINKY_SHADOW_BLUR = 20, + SLINKY_RADIUS = 60, + + ERASER_LINE_WIDTH = 1, + ERASER_SHADOW_STYLE = 'blue', + ERASER_STROKE_STYLE = 'rgba(0,0,255,0.6)', + ERASER_SHADOW_OFFSET = -5, + ERASER_SHADOW_BLUR = 20, + ERASER_RADIUS = 40, + + SHADOW_COLOR = 'rgba(0,0,0,0.7)', + + ICON_BACKGROUND_STYLE = '#eeeeee', + ICON_BORDER_STROKE_STYLE = 'rgba(100, 140, 230, 0.5)', + ICON_STROKE_STYLE = 'rgb(100, 140, 230)', + ICON_FILL_STYLE = '#dddddd', + + TEXT_ICON_FILL_STYLE = 'rgba(100, 140, 230, 0.5)', + TEXT_ICON_TEXT = 'T', + + CIRCLE_ICON_RADIUS = 20, + + ICON_RECTANGLES = [ + { x: 13.5, y: 18.5, w: 48, h: 48 }, + { x: 13.5, y: 78.5, w: 48, h: 48 }, + { x: 13.5, y: 138.5, w: 48, h: 48 }, + { x: 13.5, y: 198.5, w: 48, h: 48 }, + { x: 13.5, y: 258.5, w: 48, h: 48 }, + { x: 13.5, y: 318.5, w: 48, h: 48 }, + { x: 13.5, y: 378.5, w: 48, h: 48 }, + { x: 13.5, y: 438.5, w: 48, h: 48 }, + { x: 13.5, y: 508.5, w: 48, h: 48 } + ], + + LINE_ICON = 0, + RECTANGLE_ICON = 1, + CIRCLE_ICON = 2, + OPEN_PATH_ICON = 3, + CLOSED_PATH_ICON = 4, + CURVE_ICON = 5, + TEXT_ICON = 6, + SLINKY_ICON = 7, + ERASER_ICON = 8; + +// Grid.......................................................... + +function drawGrid(context, color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + context.globalAlpha = 0.1; + + context.beginPath(); + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + } + context.stroke(); + + context.beginPath(); + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + } + context.stroke(); + + context.restore(); +} + +// Icons......................................................... + +function drawLineIcon(rect) { + iconContext.beginPath(); + iconContext.moveTo(rect.x + 5, rect.y + 5); + iconContext.lineTo(rect.x + rect.w - 5, rect.y + rect.h - 5); + iconContext.stroke(); +} + +function drawRectIcon(rect) { + fillIconLowerRight(rect); + iconContext.strokeRect(rect.x + 5, rect.y + 5, + rect.w - 10, rect.h - 10); +} + +function drawCircleIcon(rect) { + var startAngle = 3*Math.PI/4, + endAngle = 7*Math.PI/4, + center = {x: rect.x + rect.w/2, y: rect.y + rect.h/2 }; + + fillIconLowerRight(rect); + + iconContext.beginPath(); + iconContext.arc(rect.x + rect.w/2, rect.y + rect.h/2, + CIRCLE_ICON_RADIUS, 0, Math.PI*2, false); + iconContext.stroke(); +} + +function drawOpenPathIcon(rect) { + iconContext.beginPath(); + drawOpenPathIconLines(rect); + iconContext.stroke(); +} + +function drawClosedPathIcon(rect) { + fillIconLowerRight(rect); + iconContext.beginPath(); + drawOpenPathIconLines(rect); + iconContext.closePath(); + iconContext.stroke(); +} + +function drawCurveIcon(rect) { + fillIconLowerRight(rect); + iconContext.beginPath(); + iconContext.beginPath(); + iconContext.moveTo(rect.x + rect.w - 10, rect.y + 5); + iconContext.quadraticCurveTo(rect.x - 10, rect.y, + rect.x + rect.w - 10, + rect.y + rect.h - 5); + iconContext.stroke(); +} + +function drawTextIcon(rect) { + var text = TEXT_ICON_TEXT; + + fillIconLowerRight(rect); + iconContext.fillStyle = TEXT_ICON_FILL_STYLE; + iconContext.fillText(text, rect.x + rect.w/2, + rect.y + rect.h/2 + 5); + iconContext.strokeText(text, rect.x + rect.w/2, + rect.y + rect.h/2 + 5); +} + +function drawSlinkyIcon(rect) { + var x, y; + + fillIconLowerRight(rect); + + iconContext.save(); + iconContext.strokeStyle = 'rgba(100, 140, 230, 0.6)'; + + for (var i=-2; i < rect.w/3 + 2; i+=1.5) { + if (i < rect.w/6) x = rect.x + rect.w/3 + i + rect.w/8; + else x = rect.x + rect.w/3 + (rect.w/3 - i) + rect.w/8; + + y = rect.y + rect.w/3 + i; + + iconContext.beginPath(); + iconContext.arc(x, y, 12, 0, Math.PI*2, false); + iconContext.stroke(); + } + iconContext.restore(); +} + +function drawEraserIcon(rect) { + var rect = ICON_RECTANGLES[ERASER_ICON]; + iconContext.save(); + + iconContext.beginPath(); + iconContext.arc(rect.x + rect.w/2, + rect.y + rect.h/2, + ERASER_ICON_RADIUS, 0, Math.PI*2, false); + + iconContext.strokeStyle = ERASER_ICON_CIRCLE_COLOR; + iconContext.stroke(); + + iconContext.clip(); // restrict drawGrid() to the circle + + drawGrid(iconContext, ERASER_ICON_GRID_COLOR, 5, 5); + + iconContext.restore(); +} + +function drawIcon(rect) { + iconContext.save(); + + iconContext.strokeStyle = ICON_BORDER_STROKE_STYLE; + iconContext.strokeRect(rect.x, rect.y, rect.w, rect.h); + iconContext.strokeStyle = ICON_STROKE_STYLE; + + if (rect.y === ICON_RECTANGLES[LINE_ICON].y) drawLineIcon(rect); + else if (rect.y === ICON_RECTANGLES[RECTANGLE_ICON].y) drawRectIcon(rect); + else if (rect.y === ICON_RECTANGLES[CIRCLE_ICON].y) drawCircleIcon(rect); + else if (rect.y === ICON_RECTANGLES[OPEN_PATH_ICON].y) drawOpenPathIcon(rect); + else if (rect.y === ICON_RECTANGLES[CLOSED_PATH_ICON].y) drawClosedPathIcon(rect, 20); + else if (rect.y === ICON_RECTANGLES[TEXT_ICON].y) drawTextIcon(rect); + else if (rect.y === ICON_RECTANGLES[CURVE_ICON].y) drawCurveIcon(rect); + else if (rect.y === ICON_RECTANGLES[ERASER_ICON].y) drawEraserIcon(rect); + else if (rect.y === ICON_RECTANGLES[SLINKY_ICON].y) drawSlinkyIcon(rect); + + iconContext.restore(); +} + +function drawIcons() { + iconContext.clearRect(0,0, iconCanvas.width, + iconCanvas.height); + + ICON_RECTANGLES.forEach(function(rect) { + iconContext.save(); + + if (selectedRect === rect) setSelectedIconShadow(); + else setIconShadow(); + + iconContext.fillStyle = ICON_BACKGROUND_STYLE; + iconContext.fillRect(rect.x, rect.y, rect.w, rect.h); + + iconContext.restore(); + + drawIcon(rect); + }); +} + +function drawOpenPathIconLines(rect) { + iconContext.lineTo(rect.x + 13, rect.y + 19); + iconContext.lineTo(rect.x + 15, rect.y + 17); + iconContext.lineTo(rect.x + 25, rect.y + 12); + iconContext.lineTo(rect.x + 35, rect.y + 13); + iconContext.lineTo(rect.x + 38, rect.y + 15); + iconContext.lineTo(rect.x + 40, rect.y + 17); + iconContext.lineTo(rect.x + 39, rect.y + 23); + iconContext.lineTo(rect.x + 36, rect.y + 25); + iconContext.lineTo(rect.x + 32, rect.y + 27); + iconContext.lineTo(rect.x + 28, rect.y + 29); + iconContext.lineTo(rect.x + 26, rect.y + 31); + iconContext.lineTo(rect.x + 24, rect.y + 33); + iconContext.lineTo(rect.x + 22, rect.y + 35); + iconContext.lineTo(rect.x + 20, rect.y + 37); + iconContext.lineTo(rect.x + 18, rect.y + 39); + iconContext.lineTo(rect.x + 16, rect.y + 39); + iconContext.lineTo(rect.x + 13, rect.y + 36); + iconContext.lineTo(rect.x + 11, rect.y + 34); +} + +function fillIconLowerRight(rect) { + iconContext.beginPath(); + iconContext.moveTo(rect.x + rect.w, rect.y); + iconContext.lineTo(rect.x + rect.w, rect.y + rect.h); + iconContext.lineTo(rect.x, rect.y + rect.h); + iconContext.closePath(); + iconContext.fill(); +} + +function isPointInIconLowerRight(rect, x, y) { + iconContext.beginPath(); + iconContext.moveTo(rect.x + rect.w, rect.y); + iconContext.lineTo(rect.x + rect.w, rect.y + rect.h); + iconContext.lineTo(rect.x, rect.y + rect.h); + + return iconContext.isPointInPath(x, y); +} + +function getIconFunction(rect, loc) { + var action; + + if (rect.y === ICON_RECTANGLES[LINE_ICON].y) action = 'line'; + else if (rect.y === ICON_RECTANGLES[RECTANGLE_ICON].y) action = 'rectangle'; + else if (rect.y === ICON_RECTANGLES[CIRCLE_ICON].y) action = 'circle'; + else if (rect.y === ICON_RECTANGLES[OPEN_PATH_ICON].y) action = 'path'; + else if (rect.y === ICON_RECTANGLES[CLOSED_PATH_ICON].y) action = 'pathClosed'; + else if (rect.y === ICON_RECTANGLES[CURVE_ICON].y) action = 'curve'; + else if (rect.y === ICON_RECTANGLES[TEXT_ICON].y) action = 'text'; + else if (rect.y === ICON_RECTANGLES[SLINKY_ICON].y) action = 'slinky'; + else if (rect.y === ICON_RECTANGLES[ERASER_ICON].y) action = 'erase'; + + if (action === 'rectangle' || action === 'circle' || + action === 'pathClosed' || action === 'text' || + action === 'curve' || action === 'slinky') { + doFill = isPointInIconLowerRight(rect, loc.x, loc.y); + } + + return action; +} + +function setIconShadow() { + iconContext.shadowColor = SHADOW_COLOR; + iconContext.shadowOffsetX = 1; + iconContext.shadowOffsetY = 1; + iconContext.shadowBlur = 2; +} + +function setSelectedIconShadow() { + iconContext.shadowColor = SHADOW_COLOR; + iconContext.shadowOffsetX = 4; + iconContext.shadowOffsetY = 4; + iconContext.shadowBlur = 5; +} + +function selectIcon(rect) { + selectedRect = rect; + drawIcons(); +} + +// Saving/Restoring the drawing surface.......................... + +function saveDrawingSurface() { + drawingSurfaceImageData = drawingContext.getImageData(0, 0, + drawingCanvas.width, + drawingCanvas.height); +} + +function restoreDrawingSurface() { + drawingContext.putImageData(drawingSurfaceImageData, 0, 0); +} + +// Rubberbands................................................... + +function updateRubberbandRectangle(loc) { + rubberbandW = Math.abs(loc.x - mousedown.x); + rubberbandH = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandUlhc.x = mousedown.x; + else rubberbandUlhc.x = loc.x; + + if (loc.y > mousedown.y) rubberbandUlhc.y = mousedown.y; + else rubberbandUlhc.y = loc.y; +} + +function drawRubberbandRectangle() { + drawingContext.strokeRect(rubberbandUlhc.x, + rubberbandUlhc.y, + rubberbandW, rubberbandH); +} + +function drawRubberbandLine(loc) { + drawingContext.beginPath(); + drawingContext.moveTo(mousedown.x, mousedown.y); + drawingContext.lineTo(loc.x, loc.y); + drawingContext.stroke(); +} + +function drawRubberbandCircle(loc) { + var angle = Math.atan(rubberbandH/rubberbandW); + var radius = rubberbandH / Math.sin(angle); + + if (mousedown.y === loc.y) { + radius = Math.abs(loc.x - mousedown.x); + } + + drawingContext.beginPath(); + drawingContext.arc(mousedown.x, mousedown.y, radius, 0, Math.PI*2, false); + drawingContext.stroke(); +} + +function drawRubberband(loc) { + drawingContext.save(); + + drawingContext.strokeStyle = RUBBERBAND_STROKE_STYLE; + drawingContext.lineWidth = RUBBERBAND_LINE_WIDTH; + + if (selectedFunction === 'rectangle') { + drawRubberbandRectangle(); + } + else if (selectedFunction === 'line' || + selectedFunction === 'curve') { + drawRubberbandLine(loc); + } + else if (selectedFunction === 'circle') { + drawRubberbandCircle(loc); + } + + drawingContext.restore(); +} + +// Eraser........................................................ + +function setPathForEraser() { + drawingContext.beginPath(); + drawingContext.moveTo(lastX, lastY); + drawingContext.arc(lastX, lastY, + ERASER_RADIUS + ERASER_LINE_WIDTH, + 0, Math.PI*2, false); +} + +function setSlinkyAttributes() { + drawingContext.lineWidth = lineWidthSelect.value; + drawingContext.shadowColor = strokeStyleSelect.value; + drawingContext.shadowOffsetX = SLINKY_SHADOW_OFFSET; + drawingContext.shadowOffsetY = SLINKY_SHADOW_OFFSET; + drawingContext.shadowBlur = SLINKY_SHADOW_BLUR; + drawingContext.strokeStyle = strokeStyleSelect.value; +} + +function setEraserAttributes() { + drawingContext.lineWidth = ERASER_LINE_WIDTH; + drawingContext.shadowColor = ERASER_SHADOW_STYLE; + drawingContext.shadowOffsetX = ERASER_SHADOW_OFFSET; + drawingContext.shadowOffsetY = ERASER_SHADOW_OFFSET; + drawingContext.shadowBlur = ERASER_SHADOW_BLUR; + drawingContext.strokeStyle = ERASER_STROKE_STYLE; +} + +function eraseLast() { + var x = lastX - ERASER_RADIUS-ERASER_LINE_WIDTH, + y = lastY - ERASER_RADIUS-ERASER_LINE_WIDTH, + w = ERASER_RADIUS*2+ERASER_LINE_WIDTH*2, + h = w, + cw = drawingContext.canvas.width, + ch = drawingContext.canvas.height; + + drawingContext.save(); + + setPathForEraser(); + drawingContext.clip(); + + if (x + w > cw) w = cw - x; + if (y + h > ch) h = ch - y; + + if (x < 0) { x = 0; } + if (y < 0) { y = 0; } + + drawingContext.drawImage( + backgroundContext.canvas, x, y, w, h, x, y, w, h); + + drawingContext.restore(); +} + +function drawEraser(loc) { + drawingContext.save(); + setEraserAttributes(); + + drawingContext.beginPath(); + drawingContext.arc(loc.x, loc.y, ERASER_RADIUS, + 0, Math.PI*2, false); + drawingContext.clip(); + drawingContext.stroke(); + + drawingContext.restore(); +} + +function drawSlinky(loc) { + drawingContext.save(); + setSlinkyAttributes(); + + drawingContext.beginPath(); + drawingContext.arc(loc.x, loc.y, ERASER_RADIUS, + 0, Math.PI*2, false); + drawingContext.clip(); + + drawingContext.strokeStyle = strokeStyleSelect.value; + drawingContext.stroke(); + + if (doFill) { + drawingContext.shadowColor = undefined; + drawingContext.shadowOffsetX = 0; + drawingContext.globalAlpha = 0.2; + drawingContext.fill(); + } + drawingContext.restore(); +} + +// Finish drawing lines, circles, and rectangles................. + +function finishDrawingLine(loc) { + drawingContext.beginPath(); + drawingContext.moveTo(mousedown.x, mousedown.y); + drawingContext.lineTo(loc.x, loc.y); + drawingContext.stroke(); +} + +function finishDrawingCircle(loc) { + var angle = Math.atan(rubberbandH/rubberbandW), + radius = rubberbandH / Math.sin(angle); + + if (mousedown.y === loc.y) { + radius = Math.abs(loc.x - mousedown.x); + } + + drawingContext.beginPath(); + drawingContext.arc(mousedown.x, mousedown.y, + radius, 0, Math.PI*2, false); + + if (doFill) { + drawingContext.fill(); + } + + drawingContext.stroke(); +} + +function finishDrawingRectangle() { + if (rubberbandW > 0 && rubberbandH > 0) { + if (doFill) { + drawingContext.fillRect(rubberbandUlhc.x, + rubberbandUlhc.y, + rubberbandW, rubberbandH) + } + drawingContext.strokeRect(rubberbandUlhc.x, + rubberbandUlhc.y, + rubberbandW, rubberbandH); + } +} + +// Drawing curves................................................ + +function drawControlPoint() { + drawingContext.save(); + + drawingContext.strokeStyle = CONTROL_POINT_STROKE_STYLE; + drawingContext.fillStyle = CONTROL_POINT_FILL_STYLE; + drawingContext.lineWidth = 1.0; + + drawingContext.beginPath(); + drawingContext.arc(controlPoint.x, controlPoint.y, + CONTROL_POINT_RADIUS, 0, Math.PI*2, false); + drawingContext.stroke(); + drawingContext.fill(); + + drawingContext.restore(); +} + +function startEditingCurve(loc) { + if (loc.x != mousedown.x || loc.y != mousedown.y) { + drawingCanvas.style.cursor = 'pointer'; + + curveStart.x = mousedown.x; + curveStart.y = mousedown.y; + + curveEnd.x = loc.x; + curveEnd.y = loc.y; + + controlPoint.x = (curveStart.x + curveEnd.x)/2; + controlPoint.y = (curveStart.y + curveEnd.y)/2; + + drawControlPoint(); + + editingCurve = true; + + if (showCurveInstructions) + curveInstructions.style.display = 'inline'; + } +} + +function drawCurve() { + drawingContext.beginPath(); + drawingContext.moveTo(curveStart.x, curveStart.y); + drawingContext.quadraticCurveTo(controlPoint.x, controlPoint.y, + curveEnd.x, curveEnd.y); + drawingContext.stroke(); +} + +function finishDrawingCurve() { + drawingCanvas.style.cursor = 'crosshair'; + restoreDrawingSurface(); + drawCurve(); + + if (doFill) { + drawingContext.fill(); + } +} + +// Guidewires.................................................... + +function drawHorizontalLine (y) { + drawingContext.beginPath(); + drawingContext.moveTo(0, y+0.5); + drawingContext.lineTo(drawingCanvas.width, y+0.5); + drawingContext.stroke(); +} + +function drawVerticalLine (x) { + drawingContext.beginPath(); + drawingContext.moveTo(x+0.5, 0); + drawingContext.lineTo(x+0.5, drawingCanvas.height); + drawingContext.stroke(); +} + +function drawGuidewires(x, y) { + drawingContext.save(); + drawingContext.strokeStyle = 'rgba(0,0,230,0.4)'; + drawingContext.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + drawingContext.restore(); +} + +// Event handling functions...................................... + +function windowToCanvas(canvas, x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +function mouseDownOrTouchStartInControlCanvas(loc) { + if (editingText) { + editingText = false; + eraseTextCursor(); + hideKeyboard(); + } + else if (editingCurve) { + editingCurve = false; + restoreDrawingSurface(); + } + + ICON_RECTANGLES.forEach(function(rect) { + iconContext.beginPath(); + + iconContext.rect(rect.x, rect.y, rect.w, rect.h); + if (iconContext.isPointInPath(loc.x, loc.y)) { + selectIcon(rect, loc); + selectedFunction = getIconFunction(rect, loc); + + if (selectedFunction === 'text') { + drawingCanvas.style.cursor = 'text'; + } + else { + drawingCanvas.style.cursor = 'crosshair'; + } + } + }); +}; + +// Key event handlers............................................ + +function backspace() { + restoreDrawingSurface(); + currentText = currentText.slice(0, -1); + eraseTextCursor(); +}; + +function enter() { + finishDrawingText(); + mousedown.y += drawingContext.measureText('W').width; + saveDrawingSurface(); + startDrawingText(); +}; + +function insert(key) { + currentText += key; + restoreDrawingSurface(); + drawCurrentText(); + drawTextCursor(); +}; + +document.onkeydown = function (e) { + if (e.ctrlKey || e.metaKey || e.altKey) + return; + + if (e.keyCode === 8) { // backspace + e.preventDefault(); + backspace(); + } + else if (e.keyCode === 13) { // enter + e.preventDefault(); + enter(); + } +} + +document.onkeypress = function (e) { + var key = String.fromCharCode(e.which); + + if (e.ctrlKey || e.metaKey || e.altKey) + return; + + if (editingText && e.keyCode !== 8) { + e.preventDefault(); + insert(key); + } +} + +function eraseTextCursor() { + restoreDrawingSurface(); + drawCurrentText(); +} + +function drawCurrentText() { + if (doFill) + drawingContext.fillText(currentText, mousedown.x, mousedown.y); + + drawingContext.strokeText(currentText, mousedown.x, mousedown.y); +} + +function drawTextCursor() { + var widthMetric = drawingContext.measureText(currentText), + heightMetric = drawingContext.measureText('W'), + cursorLoc = { + x: mousedown.x + widthMetric.width, + y: mousedown.y - heightMetric.width + 5 + }; + + drawingContext.beginPath(); + drawingContext.moveTo(cursorLoc.x, cursorLoc.y); + drawingContext.lineTo(cursorLoc.x, cursorLoc.y + heightMetric.width - 12); + drawingContext.stroke(); +} + +function startDrawingText() { + editingText = true; + currentText = ''; + drawTextCursor(); + showKeyboard(); +} + +function finishDrawingText() { + restoreDrawingSurface(); + drawCurrentText(); +} + +function mouseDownOrTouchStartInDrawingCanvas(loc) { + dragging = true; + + if (editingText) { + finishDrawingText(); + } + else if (editingCurve) { + if (drawingContext.isPointInPath(loc.x, loc.y)) { + draggingControlPoint = true; + } + else { + restoreDrawingSurface(); + } + editingCurve = false; + } + + if (!draggingControlPoint) { + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; + + if (selectedFunction === 'path' || selectedFunction === 'pathClosed') { + drawingContext.beginPath(); + drawingContext.moveTo(loc.x, loc.y); + } + else if (selectedFunction === 'text') { + startDrawingText(); + } + else { + editingText = false; + } + + lastX = loc.x; + lastY = loc.y; + } +} + +function moveControlPoint(loc) { + controlPoint.x = loc.x; + controlPoint.y = loc.y; +} + +function mouseMoveOrTouchMoveInDrawingCanvas(loc) { + if (draggingControlPoint) { + restoreDrawingSurface(); + + moveControlPoint(loc); + + drawingContext.save(); + + drawingContext.strokeStyle = RUBBERBAND_STROKE_STYLE; + drawingContext.lineWidth = RUBBERBAND_LINE_WIDTH; + + drawCurve(); + drawControlPoint(); + + drawingContext.restore(); + } + else if (dragging) { + if (selectedFunction === 'erase') { + eraseLast(); + drawEraser(loc); + } + else if (selectedFunction === 'slinky') { + drawSlinky(loc); + } + else if (selectedFunction === 'path' || + selectedFunction === 'pathClosed') { + drawingContext.lineTo(loc.x, loc.y); + drawingContext.stroke(); + } + else { // For lines, circles, rectangles, and curves, draw rubberbands + restoreDrawingSurface(); + updateRubberbandRectangle(loc); + drawRubberband(loc); + } + + lastX = loc.x; + lastY = loc.y; + + lastRect.w = rubberbandW; + lastRect.h = rubberbandH; + } + + if (dragging || draggingControlPoint) { + if (selectedFunction === 'line' || + selectedFunction === 'rectangle' || + selectedFunction === 'circle') { + drawGuidewires(loc.x, loc.y); + } + } +}; + +function endPath(loc) { + drawingContext.lineTo(loc.x, loc.y); + drawingContext.stroke(); + + if (selectedFunction === 'pathClosed') { + drawingContext.closePath(); + + if (doFill) { + drawingContext.fill(); + } + drawingContext.stroke(); + } +} + +function mouseUpOrTouchEndInDrawingCanvas(loc) { + if (selectedFunction !== 'erase' && selectedFunction !== 'slinky') { + restoreDrawingSurface(); + } + + if (draggingControlPoint) { + moveControlPoint(loc); + finishDrawingCurve(); + draggingControlPoint = false; + } + else if (dragging) { + if (selectedFunction === 'erase') { + eraseLast(); + } + else if (selectedFunction === 'path' || + selectedFunction === 'pathClosed') { + endPath(loc); + } + else { + if (selectedFunction === 'line') finishDrawingLine(loc); + else if (selectedFunction === 'rectangle') finishDrawingRectangle(); + else if (selectedFunction === 'circle') finishDrawingCircle(loc); + else if (selectedFunction === 'curve') startEditingCurve(loc); + } + } + dragging = false; +}; + +// Control canvas event handlers................................. + +iconCanvas.onmousedown = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + loc = windowToCanvas(iconCanvas, x, y); + + e.preventDefault(); + mouseDownOrTouchStartInControlCanvas(loc); +} + +iconCanvas.addEventListener('touchstart', function (e) { + if (e.touches.length === 1) { + e.preventDefault(); + mouseDownOrTouchStartInControlCanvas( + windowToCanvas(iconCanvas, + e.touches[0].clientX, e.touches[0].clientY)); + } +}); + +// Drawing canvas event handlers................................. + +drawingCanvas.onmousedown = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY; + + e.preventDefault(); + mouseDownOrTouchStartInDrawingCanvas( + windowToCanvas(drawingCanvas, x, y)); +} + +drawingCanvas.ontouchstart = function (e) { + if (e.touches.length === 1) { + e.preventDefault(); + mouseDownOrTouchStartInDrawingCanvas( + windowToCanvas(drawingCanvas, + e.touches[0].clientX, e.touches[0].clientY)); + } +} + +drawingCanvas.ontouchmove = function (e) { + if (e.touches.length === 1) { + mouseMoveOrTouchMoveInDrawingCanvas( + windowToCanvas(drawingCanvas, + e.touches[0].clientX, e.touches[0].clientY)); + } +} + +drawingCanvas.ontouchend = function (e) { + var loc; + + if (e.changedTouches.length === 1) { + loc = windowToCanvas(drawingCanvas, e.changedTouches[0].clientX, e.changedTouches[0].clientY); + mouseUpOrTouchEndInDrawingCanvas(loc); + } +} + +drawingCanvas.onmousemove = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + loc = windowToCanvas(drawingCanvas, x, y); + + e.preventDefault(); + mouseMoveOrTouchMoveInDrawingCanvas(loc); +} + +drawingCanvas.onmouseup = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + loc = windowToCanvas(drawingCanvas, x, y); + + e.preventDefault(); + mouseUpOrTouchEndInDrawingCanvas(loc); +} + +// Control event handlers........................................ + +strokeStyleSelect.onchange = function (e) { + drawingContext.strokeStyle = strokeStyleSelect.value; +}; + +fillStyleSelect.onchange = function (e) { + drawingContext.fillStyle = fillStyleSelect.value; +}; + +lineWidthSelect.onchange = function (e) { + drawingContext.lineWidth = lineWidthSelect.value; +/* +var c = drawingContext.canvas, + sw = c.width, + sh = c.height, + dw = sw * lineWidthSelect.value, + dh = sh * lineWidthSelect.value; + +drawingContext.scale(lineWidthSelect.value, lineWidthSelect.value); +drawingContext.drawImage(c, 0, 0); +*/ +}; + +eraseAllButton.onclick = function (e) { + drawingContext.clearRect(0,0, + drawingCanvas.width, + drawingCanvas.height); + drawGrid(drawingContext, GRID_LINE_COLOR, 10, 10); + saveDrawingSurface(); + rubberbandW = rubberbandH = 0; +}; + +curveInstructionsOkayButton.onclick = function (e) { + curveInstructions.style.display = 'none'; +}; + +curveInstructionsNoMoreButton.onclick = function (e) { + curveInstructions.style.display = 'none'; + showCurveInstructions = false; +}; + +snapshotButton.onclick = function (e) { + var dataUrl; + + if (snapshotButton.value === 'Take snapshot') { + dataUrl = drawingCanvas.toDataURL(); + snapshotImageElement.src = dataUrl; + snapshotImageElement.style.display = 'inline'; + snapshotInstructions.style.display = 'inline'; + drawingCanvas.style.display = 'none'; + iconCanvas.style.display = 'none'; + controls.style.display = 'none'; + snapshotButton.value = 'Back to Paint'; + } + else { + snapshotButton.value = 'Take snapshot'; + drawingCanvas.style.display = 'inline'; + iconCanvas.style.display = 'inline'; + controls.style.display = 'inline'; + snapshotImageElement.style.display = 'none'; + snapshotInstructions.style.display = 'none'; + } +}; + +function drawBackground() { + backgroundContext.canvas.width = drawingContext.canvas.width; + backgroundContext.canvas.height = drawingContext.canvas.height; + + drawGrid(backgroundContext, GRID_LINE_COLOR, 10, 10); +} + +// Initialization................................................ + +iconContext.strokeStyle = ICON_STROKE_STYLE; +iconContext.fillStyle = ICON_FILL_STYLE; + +iconContext.font = '48px Palatino'; +iconContext.textAlign = 'center'; +iconContext.textBaseline = 'middle'; + +drawingContext.font = '48px Palatino'; +drawingContext.textBaseline = 'bottom'; + +drawingContext.strokeStyle = strokeStyleSelect.value; +drawingContext.fillStyle = fillStyleSelect.value; +drawingContext.lineWidth = lineWidthSelect.value; + +drawGrid(drawingContext, GRID_LINE_COLOR, 10, 10); +selectedRect = ICON_RECTANGLES[SLINKY_ICON]; +selectedFunction = 'slinky'; + +// This event listener prevents touch devices from +// scrolling the visible viewport. + +document.body.addEventListener('touchmove', function (e) { + e.preventDefault(); +}, false); + +drawIcons(); +drawBackground(); diff --git a/canvas/ch02/example-2.9/example.html b/canvas/ch02/example-2.9/example.html new file mode 100644 index 0000000..819cdcf --- /dev/null +++ b/canvas/ch02/example-2.9/example.html @@ -0,0 +1,67 @@ + + + + + Stroke and Fill + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch02/example-2.9/example.js b/canvas/ch02/example-2.9/example.js new file mode 100644 index 0000000..25425be --- /dev/null +++ b/canvas/ch02/example-2.9/example.js @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'); + +// Functions.......................................................... + +function drawGrid(context, color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.lineWidth = 0.5; + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + context.closePath(); + } + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + context.closePath(); + } + context.restore(); +} + +// Initialization..................................................... + +drawGrid(context, 'lightgray', 10, 10); + +// Drawing attributes................................................. + +context.font = '48pt Helvetica'; +context.strokeStyle = 'blue'; +context.fillStyle = 'red'; +context.lineWidth = '2'; // line width set to 2 for text + +// Text............................................................... + +context.strokeText('Stroke', 60, 110); +context.fillText('Fill', 440, 110); + +context.strokeText('Stroke & Fill', 650, 110); +context.fillText('Stroke & Fill', 650, 110); + +// Rectangles......................................................... + +context.lineWidth = '5'; // line width set to 5 for shapes +context.beginPath(); +context.rect(80, 150, 150, 100); +context.stroke(); + +context.beginPath(); +context.rect(400, 150, 150, 100); +context.fill(); + +context.beginPath(); +context.rect(750, 150, 150, 100); +context.stroke(); +context.fill(); + +// Open arcs.......................................................... + +context.beginPath(); +context.arc(150, 370, 60, 0, Math.PI*3/2); +context.stroke(); + +context.beginPath(); +context.arc(475, 370, 60, 0, Math.PI*3/2); +context.fill(); + +context.beginPath(); +context.arc(820, 370, 60, 0, Math.PI*3/2); +context.stroke(); +context.fill(); + +// Closed arcs........................................................ + +context.beginPath(); +context.arc(150, 550, 60, 0, Math.PI*3/2); +context.closePath(); +context.stroke(); + +context.beginPath(); +context.arc(475, 550, 60, 0, Math.PI*3/2); +context.closePath(); +context.fill(); + +context.beginPath(); +context.arc(820, 550, 60, 0, Math.PI*3/2); +context.closePath(); +context.stroke(); +context.fill(); diff --git a/canvas/ch02/section-2.13.2.3/example.html b/canvas/ch02/section-2.13.2.3/example.html new file mode 100644 index 0000000..c58383e --- /dev/null +++ b/canvas/ch02/section-2.13.2.3/example.html @@ -0,0 +1,53 @@ + + + + + Translating, Scaling, and Rotating + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch02/section-2.13.2.3/example.js b/canvas/ch02/section-2.13.2.3/example.js new file mode 100644 index 0000000..7659216 --- /dev/null +++ b/canvas/ch02/section-2.13.2.3/example.js @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + text='Spinning', + angle = Math.PI/50, + clockwise = true, + fontHeight = 128, + origin = { }, + paused = true, + scale = 1.008; + +// Functions...................................................... + +function drawText() { + context.fillText(text, 0, 0); + context.strokeText(text, 0, 0); +} + +// Event handlers................................................. + +canvas.onclick = function() { + paused = !paused; + if (!paused) { + clockwise = !clockwise; + scale = 1/scale; + } +}; + +// Animation...................................................... + +setInterval(function() { + if (!paused) { + context.clearRect(-origin.x, -origin.y, + canvas.width, canvas.height); + + context.rotate(clockwise ? angle : -angle); + context.scale(scale, scale); + + drawText(); + } +}, 1000/60); + +// Initialization................................................. + +context.font = fontHeight + 'px Palatino'; + +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'yellow'; + +context.shadowColor = 'rgba(100, 100, 150, 0.8)'; +context.shadowOffsetX = 5; +context.shadowOffsetY = 5; +context.shadowBlur = 10; + +context.textAlign = 'center'; +context.textBaseline = 'middle'; + +origin.x = canvas.width/2; +origin.y = canvas.height/2; + +context.transform(1, 0, 0, 1, origin.x, origin.y); + +drawText(); diff --git a/canvas/ch03/example-3.1/example.html b/canvas/ch03/example-3.1/example.html new file mode 100644 index 0000000..04ee4fb --- /dev/null +++ b/canvas/ch03/example-3.1/example.html @@ -0,0 +1,68 @@ + + + + + Stroking and Filling Text + + + + +
+ Stroke + Fill + Shadow +
+ + + Canvas not supported + + + + + diff --git a/canvas/ch03/example-3.1/example.js b/canvas/ch03/example-3.1/example.js new file mode 100644 index 0000000..42bccab --- /dev/null +++ b/canvas/ch03/example-3.1/example.js @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + fillCheckbox = document.getElementById('fillCheckbox'), + strokeCheckbox = document.getElementById('strokeCheckbox'), + shadowCheckbox = document.getElementById('shadowCheckbox'), + + text='HTML5'; + +// Functions.......................................................... + +function draw() { + context.clearRect(0, 0, canvas.width, canvas.height); + drawBackground(); + + if (shadowCheckbox.checked) turnShadowsOn(); + else turnShadowsOff(); + + drawText(); +} + +function drawBackground() { // Ruled paper + var STEP_Y = 12, + TOP_MARGIN = STEP_Y * 4, + LEFT_MARGIN = STEP_Y * 3, + i = context.canvas.height; + + // Horizontal lines + + context.strokeStyle = 'lightgray'; + context.lineWidth = 0.5; + + while(i > TOP_MARGIN) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + // Vertical line + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(LEFT_MARGIN,0); + context.lineTo(LEFT_MARGIN,context.canvas.height); + context.stroke(); +} + +function turnShadowsOn() { + if (navigator.userAgent.indexOf('Opera') === -1) { + context.shadowColor = 'rgba(0, 0, 0, 0.8)'; + } + context.shadowOffsetX = 5; + context.shadowOffsetY = 5; + context.shadowBlur = 10; +} + +function turnShadowsOff() { + if (navigator.userAgent.indexOf('Opera') === -1) { + context.shadowColor = undefined; + } + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; +} + +function drawText() { + var TEXT_X = 65, + TEXT_Y = canvas.height/2 + 35; + + context.strokeStyle = 'blue'; + + if (fillCheckbox.checked) context.fillText (text, TEXT_X, TEXT_Y); + if (strokeCheckbox.checked) context.strokeText(text, TEXT_X, TEXT_Y); +} + +// Event handlers..................................................... + +fillCheckbox.onchange = draw; +strokeCheckbox.onchange = draw; +shadowCheckbox.onchange = draw; + +// Initialization..................................................... + +context.font = '128px Palatino'; +context.lineWidth = 1.0; +context.fillStyle = 'cornflowerblue'; + +turnShadowsOn(); + +draw(); diff --git a/canvas/ch03/example-3.12/example.html b/canvas/ch03/example-3.12/example.html new file mode 100644 index 0000000..9c62d54 --- /dev/null +++ b/canvas/ch03/example-3.12/example.html @@ -0,0 +1,98 @@ + + + + + + A Simple Text Cursor + + + + + + + Canvas not supported + + +
+ Font: + + Size: + + Fill color: +
+ + + + + diff --git a/canvas/ch03/example-3.12/example.js b/canvas/ch03/example-3.12/example.js new file mode 100644 index 0000000..8a8efed --- /dev/null +++ b/canvas/ch03/example-3.12/example.js @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + fontSelect = document.getElementById('fontSelect'), + sizeSelect = document.getElementById('sizeSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + cursorWidthStyleSelect = document.getElementById('cursorWidthStyleSelect'), + + GRID_STROKE_STYLE = 'lightgray', + GRID_HORIZONTAL_SPACING = 10, + GRID_VERTICAL_SPACING = 10, + + TEXT_CURSOR_LINE_WIDTH = 2, + TEXT_CURSOR_WIDTH = 4, + cursor = new TextCursor(); + +// General-purpose functions..................................... + +function drawGrid(color, stepx, stepy) { + context.strokeStyle = color; + context.lineWidth = 0.5; + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } +} + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Drawing surface............................................... + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +// Text.......................................................... + +function setFont() { + context.font = sizeSelect.value + 'px ' + fontSelect.value; +} + +function moveCursor(loc) { + cursor.draw(context, loc.x, loc.y); +} + +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e); + moveCursor(loc); +}; + +fillStyleSelect.onchange = function (e) { + cursor.fillStyle = fillStyleSelect.value; +} + +// Initialization................................................ + +fontSelect.onchange = setFont; +sizeSelect.onchange = setFont; + +cursor.width = 2; + +context.lineWidth = 2.0; +setFont(); +/* +drawGrid(GRID_STROKE_STYLE, + GRID_HORIZONTAL_SPACING, + GRID_VERTICAL_SPACING); + +saveDrawingSurface(); +*/ +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'rgba(0,0,200,0.225)'; + context.lineWidth = 0.5; + + context.save(); + context.restore(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +drawBackground(); +saveDrawingSurface(); diff --git a/canvas/ch03/example-3.12/text.js b/canvas/ch03/example-3.12/text.js new file mode 100644 index 0000000..cae3a24 --- /dev/null +++ b/canvas/ch03/example-3.12/text.js @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Cursor......................................................... + +TextCursor = function (width, fillStyle) { + this.fillStyle = fillStyle || 'rgba(0, 0, 0, 0.5)'; + this.width = width || 2; + this.left = 0; + this.top = 0; +}; + +TextCursor.prototype = { + getHeight: function (context) { + return context.measureText('W').width; + }, + + createPath: function (context) { + context.beginPath(); + context.rect(this.left, this.top, + this.width, this.getHeight(context)); + }, + + draw: function (context, left, bottom) { + context.save(); + + this.left = left; + this.top = bottom - this.getHeight(context); + + this.createPath(context); + + context.fillStyle = this.fillStyle; + context.fill(); + + context.restore(); + }, +}; diff --git a/canvas/ch03/example-3.14/example.html b/canvas/ch03/example-3.14/example.html new file mode 100644 index 0000000..aae1fa5 --- /dev/null +++ b/canvas/ch03/example-3.14/example.html @@ -0,0 +1,97 @@ + + + + + + Inserting Text into a Canvas + + + + + + + Canvas not supported + + +
+ Font: + + Size: + + Fill color: +
+ + + + + diff --git a/canvas/ch03/example-3.14/example.js b/canvas/ch03/example-3.14/example.js new file mode 100644 index 0000000..732310a --- /dev/null +++ b/canvas/ch03/example-3.14/example.js @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + fontSelect = document.getElementById('fontSelect'), + sizeSelect = document.getElementById('sizeSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + cursorWidthStyleSelect = document.getElementById('cursorWidthStyleSelect'), + + GRID_STROKE_STYLE = 'lightgray', + GRID_HORIZONTAL_SPACING = 10, + GRID_VERTICAL_SPACING = 10, + + TEXT_CURSOR_LINE_WIDTH = 2, + TEXT_CURSOR_WIDTH = 4, + cursor = new TextCursor(); + +// General-purpose functions..................................... + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'rgba(0,0,200,0.225)'; + context.lineWidth = 0.5; + + context.save(); + context.restore(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Drawing surface............................................... + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +// Text.......................................................... + +function setFont() { + context.font = sizeSelect.value + 'px ' + fontSelect.value; +} + +function moveCursor(loc) { + cursor.erase(context, drawingSurfaceImageData); + cursor.draw(context, loc.x, loc.y); +} + +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e); + moveCursor(loc); +}; + +fillStyleSelect.onchange = function (e) { + cursor.fillStyle = fillStyleSelect.value; +} + +// Initialization................................................ + +fontSelect.onchange = setFont; +sizeSelect.onchange = setFont; + +cursor.width = 2; + +context.lineWidth = 2.0; +setFont(); + +drawBackground(); +saveDrawingSurface(); diff --git a/canvas/ch03/example-3.14/text.js b/canvas/ch03/example-3.14/text.js new file mode 100644 index 0000000..f164097 --- /dev/null +++ b/canvas/ch03/example-3.14/text.js @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Cursor......................................................... + +TextCursor = function (width, fillStyle) { + this.fillStyle = fillStyle || 'rgba(0, 0, 0, 0.5)'; + this.width = width || 2; + this.left = 0; + this.top = 0; +}; + +TextCursor.prototype = { + getHeight: function (context) { + return context.measureText('W').width; + }, + + createPath: function (context) { + context.beginPath(); + context.rect(this.left, this.top, + this.width, this.getHeight(context)); + }, + + draw: function (context, left, bottom) { + context.save(); + + this.left = left; + this.top = bottom - this.getHeight(context); + + this.createPath(context); + + context.fillStyle = this.fillStyle; + context.fill(); + + context.restore(); + }, + + erase: function (context, imageData) { + context.putImageData(imageData, 0, 0, + this.left, this.top, + this.width, this.getHeight(context)); + } +}; diff --git a/canvas/ch03/example-3.15/example.html b/canvas/ch03/example-3.15/example.html new file mode 100644 index 0000000..8024cfe --- /dev/null +++ b/canvas/ch03/example-3.15/example.html @@ -0,0 +1,107 @@ + + + + + + Lines + + + + + + + Canvas not supported + + +
+ Font: + + Size: + + Text stroke color: + + Text fill color: +
+ + + + + diff --git a/canvas/ch03/example-3.15/example.js b/canvas/ch03/example-3.15/example.js new file mode 100644 index 0000000..8146f5d --- /dev/null +++ b/canvas/ch03/example-3.15/example.js @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + fontSelect = document.getElementById('fontSelect'), + sizeSelect = document.getElementById('sizeSelect'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + + GRID_STROKE_STYLE = 'lightgray', + GRID_HORIZONTAL_SPACING = 10, + GRID_VERTICAL_SPACING = 10, + + cursor = new TextCursor(), + + line, + + blinkingInterval, + BLINK_TIME = 1000, + BLINK_OFF = 300; + +// General-purpose functions..................................... + +function drawBackground() { // Ruled paper + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'rgba(0,0,200,0.225)'; + context.lineWidth = 0.5; + + context.save(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Drawing surface............................................... + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +// Text.......................................................... + +function setFont() { + context.font = sizeSelect.value + 'px ' + fontSelect.value; +} + +function blinkCursor(x, y) { + clearInterval(blinkingInterval); + blinkingInterval = setInterval( function (e) { + cursor.erase(context, drawingSurfaceImageData); + + setTimeout( function (e) { + if (cursor.left == x && + cursor.top + cursor.getHeight(context) == y) { + cursor.draw(context, x, y); + } + }, 300); + }, 1000); +} + +function moveCursor(x, y) { + cursor.erase(context, drawingSurfaceImageData); + saveDrawingSurface(); + context.putImageData(drawingSurfaceImageData, 0, 0); + + cursor.draw(context, x, y); + blinkCursor(x, y); +} + +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY), + fontHeight = context.measureText('W').width; + + fontHeight += fontHeight/6; + line = new TextLine(loc.x, loc.y); + moveCursor(loc.x, loc.y); +}; + +fillStyleSelect.onchange = function (e) { + cursor.fillStyle = fillStyleSelect.value; + context.fillStyle = fillStyleSelect.value; +} + +strokeStyleSelect.onchange = function (e) { + cursor.strokeStyle = strokeStyleSelect.value; + context.strokeStyle = strokeStyleSelect.value; +} + +// Key event handlers............................................ + +document.onkeydown = function (e) { + if (e.keyCode === 8 || e.keyCode === 13) { + // The call to e.preventDefault() suppresses + // the browser's subsequent call to document.onkeypress(), + // so only suppress that call for backspace and enter. + e.preventDefault(); + } + + if (e.keyCode === 8) { // backspace + context.save(); + + line.erase(context, drawingSurfaceImageData); + line.removeCharacterBeforeCaret(); + + moveCursor(line.left + line.getWidth(context), + line.bottom); + + line.draw(context); + + context.restore(); + } +} + +document.onkeypress = function (e) { + var key = String.fromCharCode(e.which); + + if (e.keyCode !== 8 && !e.ctrlKey && !e.metaKey) { + e.preventDefault(); // no further browser processing + + context.save(); + + line.erase(context, drawingSurfaceImageData); + line.insert(key); + + moveCursor(line.left + line.getWidth(context), + line.bottom); + + context.shadowColor = 'rgba(0, 0, 0, 0.5)'; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.shadowBlur = 2; + + line.draw(context); + + context.restore(); + } +} + +// Initialization................................................ + +fontSelect.onchange = setFont; +sizeSelect.onchange = setFont; + +cursor.fillStyle = fillStyleSelect.value; +cursor.strokeStyle = strokeStyleSelect.value; + +context.fillStyle = fillStyleSelect.value; +context.strokeStyle = strokeStyleSelect.value; + +context.lineWidth = 2.0; + +setFont(); +drawBackground(); +saveDrawingSurface(); diff --git a/canvas/ch03/example-3.15/text.js b/canvas/ch03/example-3.15/text.js new file mode 100644 index 0000000..04bc004 --- /dev/null +++ b/canvas/ch03/example-3.15/text.js @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Cursor......................................................... + +TextCursor = function (fillStyle, width) { + this.fillStyle = fillStyle || 'rgba(0, 0, 0, 0.7)'; + this.width = width || 2; + this.left = 0; + this.top = 0; +}; + +TextCursor.prototype = { + getHeight: function (context) { + var h = context.measureText('M').width; + return h + h/6; + }, + + createPath: function (context) { + context.beginPath(); + context.rect(this.left, this.top, + this.width, this.getHeight(context)); + }, + + draw: function (context, left, bottom) { + context.save(); + + this.left = left; + this.top = bottom - this.getHeight(context); + + this.createPath(context); + + context.fillStyle = this.fillStyle; + context.fill(); + + context.restore(); + }, + + erase: function (context, imageData) { + context.putImageData(imageData, 0, 0, + this.left, this.top, + this.width, this.getHeight(context)); + } +}; + +// Text lines..................................................... + +TextLine = function (x, y) { + this.text = ''; + this.left = x; + this.bottom = y; + this.caret = 0; +}; + +TextLine.prototype = { + insert: function (text) { + this.text = this.text.substr(0, this.caret) + text + + this.text.substr(this.caret); + this.caret += text.length; + }, + + removeCharacterBeforeCaret: function () { + if (this.caret === 0) + return; + + this.text = this.text.substring(0, this.caret-1) + + this.text.substring(this.caret); + + this.caret--; + }, + + getWidth: function(context) { + return context.measureText(this.text).width; + }, + + getHeight: function (context) { + var h = context.measureText('W').width; + return h + h/6; + }, + + draw: function(context) { + context.save(); + context.textAlign = 'start'; + context.textBaseline = 'bottom'; + + context.strokeText(this.text, this.left, this.bottom); + context.fillText(this.text, this.left, this.bottom); + + context.restore(); + }, + + erase: function (context, imageData) { + context.putImageData(imageData, 0, 0); + } +}; diff --git a/canvas/ch03/example-3.17/example.html b/canvas/ch03/example-3.17/example.html new file mode 100644 index 0000000..0346aac --- /dev/null +++ b/canvas/ch03/example-3.17/example.html @@ -0,0 +1,107 @@ + + + + + + Lines + + + + + + + Canvas not supported + + +
+ Font: + + Size: + + Text stroke color: + + Text fill color: +
+ + + + + diff --git a/canvas/ch03/example-3.17/example.js b/canvas/ch03/example-3.17/example.js new file mode 100644 index 0000000..8146f5d --- /dev/null +++ b/canvas/ch03/example-3.17/example.js @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + fontSelect = document.getElementById('fontSelect'), + sizeSelect = document.getElementById('sizeSelect'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + + GRID_STROKE_STYLE = 'lightgray', + GRID_HORIZONTAL_SPACING = 10, + GRID_VERTICAL_SPACING = 10, + + cursor = new TextCursor(), + + line, + + blinkingInterval, + BLINK_TIME = 1000, + BLINK_OFF = 300; + +// General-purpose functions..................................... + +function drawBackground() { // Ruled paper + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'rgba(0,0,200,0.225)'; + context.lineWidth = 0.5; + + context.save(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Drawing surface............................................... + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +// Text.......................................................... + +function setFont() { + context.font = sizeSelect.value + 'px ' + fontSelect.value; +} + +function blinkCursor(x, y) { + clearInterval(blinkingInterval); + blinkingInterval = setInterval( function (e) { + cursor.erase(context, drawingSurfaceImageData); + + setTimeout( function (e) { + if (cursor.left == x && + cursor.top + cursor.getHeight(context) == y) { + cursor.draw(context, x, y); + } + }, 300); + }, 1000); +} + +function moveCursor(x, y) { + cursor.erase(context, drawingSurfaceImageData); + saveDrawingSurface(); + context.putImageData(drawingSurfaceImageData, 0, 0); + + cursor.draw(context, x, y); + blinkCursor(x, y); +} + +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(e.clientX, e.clientY), + fontHeight = context.measureText('W').width; + + fontHeight += fontHeight/6; + line = new TextLine(loc.x, loc.y); + moveCursor(loc.x, loc.y); +}; + +fillStyleSelect.onchange = function (e) { + cursor.fillStyle = fillStyleSelect.value; + context.fillStyle = fillStyleSelect.value; +} + +strokeStyleSelect.onchange = function (e) { + cursor.strokeStyle = strokeStyleSelect.value; + context.strokeStyle = strokeStyleSelect.value; +} + +// Key event handlers............................................ + +document.onkeydown = function (e) { + if (e.keyCode === 8 || e.keyCode === 13) { + // The call to e.preventDefault() suppresses + // the browser's subsequent call to document.onkeypress(), + // so only suppress that call for backspace and enter. + e.preventDefault(); + } + + if (e.keyCode === 8) { // backspace + context.save(); + + line.erase(context, drawingSurfaceImageData); + line.removeCharacterBeforeCaret(); + + moveCursor(line.left + line.getWidth(context), + line.bottom); + + line.draw(context); + + context.restore(); + } +} + +document.onkeypress = function (e) { + var key = String.fromCharCode(e.which); + + if (e.keyCode !== 8 && !e.ctrlKey && !e.metaKey) { + e.preventDefault(); // no further browser processing + + context.save(); + + line.erase(context, drawingSurfaceImageData); + line.insert(key); + + moveCursor(line.left + line.getWidth(context), + line.bottom); + + context.shadowColor = 'rgba(0, 0, 0, 0.5)'; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.shadowBlur = 2; + + line.draw(context); + + context.restore(); + } +} + +// Initialization................................................ + +fontSelect.onchange = setFont; +sizeSelect.onchange = setFont; + +cursor.fillStyle = fillStyleSelect.value; +cursor.strokeStyle = strokeStyleSelect.value; + +context.fillStyle = fillStyleSelect.value; +context.strokeStyle = strokeStyleSelect.value; + +context.lineWidth = 2.0; + +setFont(); +drawBackground(); +saveDrawingSurface(); diff --git a/canvas/ch03/example-3.17/text.js b/canvas/ch03/example-3.17/text.js new file mode 100644 index 0000000..04bc004 --- /dev/null +++ b/canvas/ch03/example-3.17/text.js @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Cursor......................................................... + +TextCursor = function (fillStyle, width) { + this.fillStyle = fillStyle || 'rgba(0, 0, 0, 0.7)'; + this.width = width || 2; + this.left = 0; + this.top = 0; +}; + +TextCursor.prototype = { + getHeight: function (context) { + var h = context.measureText('M').width; + return h + h/6; + }, + + createPath: function (context) { + context.beginPath(); + context.rect(this.left, this.top, + this.width, this.getHeight(context)); + }, + + draw: function (context, left, bottom) { + context.save(); + + this.left = left; + this.top = bottom - this.getHeight(context); + + this.createPath(context); + + context.fillStyle = this.fillStyle; + context.fill(); + + context.restore(); + }, + + erase: function (context, imageData) { + context.putImageData(imageData, 0, 0, + this.left, this.top, + this.width, this.getHeight(context)); + } +}; + +// Text lines..................................................... + +TextLine = function (x, y) { + this.text = ''; + this.left = x; + this.bottom = y; + this.caret = 0; +}; + +TextLine.prototype = { + insert: function (text) { + this.text = this.text.substr(0, this.caret) + text + + this.text.substr(this.caret); + this.caret += text.length; + }, + + removeCharacterBeforeCaret: function () { + if (this.caret === 0) + return; + + this.text = this.text.substring(0, this.caret-1) + + this.text.substring(this.caret); + + this.caret--; + }, + + getWidth: function(context) { + return context.measureText(this.text).width; + }, + + getHeight: function (context) { + var h = context.measureText('W').width; + return h + h/6; + }, + + draw: function(context) { + context.save(); + context.textAlign = 'start'; + context.textBaseline = 'bottom'; + + context.strokeText(this.text, this.left, this.bottom); + context.fillText(this.text, this.left, this.bottom); + + context.restore(); + }, + + erase: function (context, imageData) { + context.putImageData(imageData, 0, 0); + } +}; diff --git a/canvas/ch03/example-3.18/example.html b/canvas/ch03/example-3.18/example.html new file mode 100644 index 0000000..9812462 --- /dev/null +++ b/canvas/ch03/example-3.18/example.html @@ -0,0 +1,109 @@ + + + + + + Paragraphs + + + + + + + Canvas not supported + + +
+ Font: + + Size: + + Text stroke color: + + Text fill color: + + Fill: +
+ + + + + diff --git a/canvas/ch03/example-3.18/example.js b/canvas/ch03/example-3.18/example.js new file mode 100644 index 0000000..dc55ad8 --- /dev/null +++ b/canvas/ch03/example-3.18/example.js @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + fontSelect = document.getElementById('fontSelect'), + sizeSelect = document.getElementById('sizeSelect'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + + GRID_STROKE_STYLE = 'lightgray', + GRID_HORIZONTAL_SPACING = 10, + GRID_VERTICAL_SPACING = 10, + + drawingSurfaceImageData, + + cursor = new TextCursor(), + paragraph; + +// General-purpose functions..................................... + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'rgba(0,0,200,0.225)'; + context.lineWidth = 0.5; + + context.save(); + context.restore(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function windowToCanvas(canvas, x, y) { + var bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +// Drawing surface............................................... + +function saveDrawingSurface() { + drawingSurfaceImageData = context.getImageData(0, 0, + canvas.width, + canvas.height); +} + +// Text.......................................................... + +function setFont() { + context.font = sizeSelect.value + 'px ' + fontSelect.value; +} + +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(canvas, e.clientX, e.clientY), + fontHeight, + line; + + cursor.erase(context, drawingSurfaceImageData); + saveDrawingSurface(); + + if (paragraph && paragraph.isPointInside(loc)) { + paragraph.moveCursorCloseTo(loc.x, loc.y); + } + else { + fontHeight = context.measureText('W').width, + fontHeight += fontHeight/6; + + paragraph = new Paragraph(context, loc.x, loc.y - fontHeight, + drawingSurfaceImageData, + cursor); + + paragraph.addLine(new TextLine(loc.x, loc.y)); + } +}; + +fillStyleSelect.onchange = function (e) { + cursor.fillStyle = fillStyleSelect.value; +} + +strokeStyleSelect.onchange = function (e) { + cursor.strokeStyle = strokeStyleSelect.value; +} + +// Key event handlers............................................ + +document.onkeydown = function (e) { + if (e.keyCode === 8 || e.keyCode === 13) { + // The call to e.preventDefault() suppresses + // the browser's subsequent call to document.onkeypress(), + // so only suppress that call for backspace and enter. + e.preventDefault(); + } + + if (e.keyCode === 8) { // backspace + paragraph.backspace(); + } + else if (e.keyCode === 13) { // enter + paragraph.newline(); + } +} + +document.onkeypress = function (e) { + var key = String.fromCharCode(e.which); + + // Only process if user is editing text + // and they aren't holding down the CTRL + // or META keys. + + if (e.keyCode !== 8 && !e.ctrlKey && !e.metaKey) { + e.preventDefault(); // no further browser processing + + context.fillStyle = fillStyleSelect.value; + context.strokeStyle = strokeStyleSelect.value; + + paragraph.insert(key); + } +} + +// Initialization................................................ + +fontSelect.onchange = setFont; +sizeSelect.onchange = setFont; + +cursor.fillStyle = fillStyleSelect.value; +cursor.strokeStyle = strokeStyleSelect.value; + +context.lineWidth = 2.0; +setFont(); + +drawBackground(); +saveDrawingSurface(); diff --git a/canvas/ch03/example-3.18/text.js b/canvas/ch03/example-3.18/text.js new file mode 100644 index 0000000..4c88984 --- /dev/null +++ b/canvas/ch03/example-3.18/text.js @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Cursor......................................................... + +TextCursor = function (fillStyle, width) { + this.fillStyle = fillStyle || 'rgba(0, 0, 0, 0.7)'; + this.width = width || 2; + this.left = 0; + this.top = 0; +}; + +TextCursor.prototype = { + getHeight: function (context) { + var w = context.measureText('W').width; + return w + w/6; + }, + + createPath: function (context) { + context.beginPath(); + context.rect(this.left, this.top, + this.width, this.getHeight(context)); + }, + + draw: function (context, left, bottom) { + context.save(); + + this.left = left; + this.top = bottom - this.getHeight(context); + + this.createPath(context); + + context.fillStyle = this.fillStyle; + context.fill(); + + context.restore(); + }, + + erase: function (context, imageData) { + context.putImageData(imageData, 0, 0, + this.left, this.top, + this.width, this.getHeight(context)); + } +}; + +// Text lines..................................................... + +TextLine = function (x, y) { + this.text = ''; + this.left = x; + this.bottom = y; + this.caret = 0; +}; + +TextLine.prototype = { + insert: function (text) { + var first = this.text.slice(0, this.caret), + last = this.text.slice(this.caret); + + first += text; + this.text = first; + this.text += last; + this.caret += text.length; + }, + + getCaretX: function (context) { + var s = this.text.substring(0, this.caret), + w = context.measureText(s).width; + + return this.left + w; + }, + + removeCharacterBeforeCaret: function () { + if (this.caret === 0) + return; + + this.text = this.text.substring(0, this.caret-1) + + this.text.substring(this.caret); + + this.caret--; + }, + + removeLastCharacter: function () { + this.text = this.text.slice(0, -1); + }, + + getWidth: function(context) { + return context.measureText(this.text).width; + }, + + getHeight: function (context) { + var h = context.measureText('W').width; + return h + h/6; + }, + + draw: function(context) { + context.save(); + context.textAlign = 'start'; + context.textBaseline = 'bottom'; + + context.strokeText(this.text, this.left, this.bottom); + context.fillText(this.text, this.left, this.bottom); + + context.restore(); + }, + + erase: function (context, imageData) { + context.putImageData(imageData, 0, 0); + } +}; + +// Paragraphs..................................................... + +Paragraph = function (context, left, top, imageData, cursor) { + this.context = context; + this.drawingSurface = imageData; + this.left = left; + this.top = top; + this.lines = []; + this.activeLine = undefined; + this.cursor = cursor; + this.blinkingInterval = undefined; +}; + +Paragraph.prototype = { + isPointInside: function (loc) { + var c = this.context; + + c.beginPath(); + c.rect(this.left, this.top, + this.getWidth(), this.getHeight()); + + return c.isPointInPath(loc.x, loc.y); + }, + + getHeight: function () { + var h = 0; + + this.lines.forEach( function (line) { + h += line.getHeight(this.context); + }); + + return h; + }, + + getWidth: function () { + var w = 0, + widest = 0; + + this.lines.forEach( function (line) { + w = line.getWidth(this.context); + if (w > widest) { + widest = w; + } + }); + + return widest; + }, + + draw: function () { + this.lines.forEach( function (line) { + line.draw(this.context); + }); + }, + + erase: function (context, imageData) { + context.putImageData(imageData, 0, 0); + }, + + addLine: function (line) { + this.lines.push(line); + this.activeLine = line; + this.moveCursor(line.left, line.bottom); + }, + + insert: function (text) { + this.erase(this.context, this.drawingSurface); + this.activeLine.insert(text); + + var t = this.activeLine.text.substring(0, this.activeLine.caret), + w = this.context.measureText(t).width; + + this.moveCursor(this.activeLine.left + w, + this.activeLine.bottom); + + this.draw(this.context); + }, + + blinkCursor: function (x, y) { + var self = this, + BLINK_OUT = 200, + BLINK_INTERVAL = 900; + + this.blinkingInterval = setInterval( function (e) { + cursor.erase(context, self.drawingSurface); + + setTimeout( function (e) { + cursor.draw(context, cursor.left, + cursor.top + cursor.getHeight(context)); + }, BLINK_OUT); + }, BLINK_INTERVAL); + }, + + moveCursorCloseTo: function (x, y) { + var line = this.getLine(y); + + if (line) { + line.caret = this.getColumn(line, x); + this.activeLine = line; + this.moveCursor(line.getCaretX(context), + line.bottom); + } + }, + + moveCursor: function (x, y) { + this.cursor.erase(this.context, this.drawingSurface); + this.cursor.draw(this.context, x, y); + + if ( ! this.blinkingInterval) + this.blinkCursor(x, y); + }, + + moveLinesDown: function (start) { + for (var i=start; i < this.lines.length; ++i) { + line = this.lines[i]; + line.bottom += line.getHeight(this.context); + } + }, + + newline: function () { + var textBeforeCursor = this.activeLine.text.substring(0, this.activeLine.caret), + textAfterCursor = this.activeLine.text.substring(this.activeLine.caret), + height = this.context.measureText('W').width + + this.context.measureText('W').width/6, + bottom = this.activeLine.bottom + height, + activeIndex, + line; + + this.erase(this.context, this.drawingSurface); // Erase paragraph + this.activeLine.text = textBeforeCursor; // Set active line's text + + line = new TextLine(this.activeLine.left, bottom); // Create a new line + line.insert(textAfterCursor); // containing text after cursor + + activeIndex = this.lines.indexOf(this.activeLine); // Splice in new line + this.lines.splice(activeIndex+1, 0, line); + + this.activeLine = line; // New line is active with + this.activeLine.caret = 0; // caret at first character + + activeIndex = this.lines.indexOf(this.activeLine); // Starting at the new line... + + for(var i=activeIndex+1; i < this.lines.length; ++i) { //...loop over remaining lines + line = this.lines[i]; + line.bottom += height; // move line down one row + } + + this.draw(); + this.cursor.draw(this.context, this.activeLine.left, this.activeLine.bottom); + }, + + getLine: function (y) { + var line; + + for (i=0; i < this.lines.length; ++i) { + line = this.lines[i]; + if (y > line.bottom - line.getHeight(context) && + y < line.bottom) { + return line; + } + } + return undefined; + }, + + getColumn: function (line, x) { + var found = false, + before, + after, + closest, + tmpLine, + column; + + tmpLine = new TextLine(line.left, line.bottom); + tmpLine.insert(line.text); + + while ( ! found && tmpLine.text.length > 0) { + before = tmpLine.left + tmpLine.getWidth(context); + tmpLine.removeLastCharacter(); + after = tmpLine.left + tmpLine.getWidth(context); + + if (after < x) { + closest = x - after < before - x ? after : before; + column = closest === before ? + tmpLine.text.length + 1 : tmpLine.text.length; + found = true; + } + } + return column; + }, + + activeLineIsOutOfText: function () { + return this.activeLine.text.length === 0; + }, + + activeLineIsTopLine: function () { + return this.lines[0] === this.activeLine; + }, + + moveUpOneLine: function () { + var lastActiveText, line, before, after; + + lastActiveLine = this.activeLine; + lastActiveText = '' + lastActiveLine.text; + + activeIndex = this.lines.indexOf(this.activeLine); + this.activeLine = this.lines[activeIndex - 1]; + this.activeLine.caret = this.activeLine.text.length; + + this.lines.splice(activeIndex, 1); + + this.moveCursor( + this.activeLine.left + this.activeLine.getWidth(this.context), + this.activeLine.bottom); + + this.activeLine.text += lastActiveText; + + for (var i=activeIndex; i < this.lines.length; ++i) { + line = this.lines[i]; + line.bottom -= line.getHeight(this.context); + } + }, + + backspace: function () { + var lastActiveLine, + activeIndex, + t, w; + + this.context.save(); + + if (this.activeLine.caret === 0) { + if ( ! this.activeLineIsTopLine()) { + this.erase(this.context, this.drawingSurface); + this.moveUpOneLine(); + this.draw(); + } + } + else { // active line has text + this.context.fillStyle = fillStyleSelect.value; + this.context.strokeStyle = strokeStyleSelect.value; + + this.erase(this.context, this.drawingSurface); + this.activeLine.removeCharacterBeforeCaret(); + + t = this.activeLine.text.slice(0, this.activeLine.caret), + w = this.context.measureText(t).width; + + this.moveCursor(this.activeLine.left + w, + this.activeLine.bottom); + + this.draw(this.context); + + context.restore(); + } + } +}; diff --git a/canvas/ch03/example-3.2/cloth.png b/canvas/ch03/example-3.2/cloth.png new file mode 100644 index 0000000..57ba745 Binary files /dev/null and b/canvas/ch03/example-3.2/cloth.png differ diff --git a/canvas/ch03/example-3.2/example.html b/canvas/ch03/example-3.2/example.html new file mode 100644 index 0000000..c85d79c --- /dev/null +++ b/canvas/ch03/example-3.2/example.html @@ -0,0 +1,63 @@ + + + + + Filling Text with Gradients and Patterns + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch03/example-3.2/example.js b/canvas/ch03/example-3.2/example.js new file mode 100644 index 0000000..5b34380 --- /dev/null +++ b/canvas/ch03/example-3.2/example.js @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + image = new Image(), + gradient = context.createLinearGradient(0, 0, + canvas.width, canvas.height), + text = 'Canvas', + pattern; // create pattern after image loads + +// Functions............................................................ + +function drawBackground() { + var STEP_Y = 12, + TOP_MARGIN = STEP_Y*4, + LEFT_MARGIN = 35, + i = context.canvas.height; + + context.save(); + + context.strokeStyle = 'lightgray'; + context.lineWidth = 0.5; + + while(i > TOP_MARGIN) { // Draw horizontal lines from bottom up + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + // Draw vertical line + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + context.moveTo(LEFT_MARGIN, 0); + context.lineTo(LEFT_MARGIN, context.canvas.height); + context.stroke(); + + context.restore(); +} + +function drawGradientText() { + context.fillStyle = gradient; + context.fillText(text, 65, 200); + context.strokeText(text, 65, 200); +} + +function drawPatternText() { + context.fillStyle = pattern; + context.fillText(text, 65, 450); + context.strokeText(text, 65, 450); +} + +// Event Handlers....................................................... + +image.onload = function (e) { + pattern = context.createPattern(image, 'repeat'); + drawPatternText(); +}; + +// Initialization....................................................... + +image.src = 'redball.png'; + +context.font = '256px Palatino'; +context.strokeStyle = 'cornflowerblue'; + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(100, 100, 150, 0.8)'; + +context.shadowOffsetX = 5; +context.shadowOffsetY = 5; +context.shadowBlur = 10; + +gradient.addColorStop(0, 'blue'); +gradient.addColorStop(0.25, 'blue'); +gradient.addColorStop(0.5, 'white'); +gradient.addColorStop(0.75, 'red'); +gradient.addColorStop(1.0, 'yellow'); + +drawBackground(); +drawGradientText(); diff --git a/canvas/ch03/example-3.2/redball.png b/canvas/ch03/example-3.2/redball.png new file mode 100644 index 0000000..4274f3e Binary files /dev/null and b/canvas/ch03/example-3.2/redball.png differ diff --git a/canvas/ch03/example-3.3/example.html b/canvas/ch03/example-3.3/example.html new file mode 100644 index 0000000..3ee2672 --- /dev/null +++ b/canvas/ch03/example-3.3/example.html @@ -0,0 +1,63 @@ + + + + + Specifying Fonts + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch03/example-3.3/example.js b/canvas/ch03/example-3.3/example.js new file mode 100644 index 0000000..4f35da1 --- /dev/null +++ b/canvas/ch03/example-3.3/example.js @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + LEFT_COLUMN_FONTS = [ + '2em palatino', 'bolder 2em palatino', + 'lighter 2em palatino', 'italic 2em palatino', + 'oblique small-caps 24px palatino', 'bold 14pt palatino', + 'xx-large palatino', 'italic xx-large palatino' + ], + + RIGHT_COLUMN_FONTS = [ + 'oblique 1.5em lucida console', 'x-large fantasy', + 'italic 28px monaco', 'italic large copperplate', + '36px century', '28px tahoma', + '28px impact', '1.7em verdana' + ], + + LEFT_COLUMN_X = 50, + RIGHT_COLUMN_X = 500, + DELTA_Y = 50, + TOP_Y = 50, + y = 0; + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'rgba(0,0,200,0.225)'; + context.lineWidth = 0.5; + + context.save(); + context.restore(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +drawBackground(); + +context.fillStyle = 'blue', + +LEFT_COLUMN_FONTS.forEach( function (font) { + context.font = font; + context.fillText(font, LEFT_COLUMN_X, y += DELTA_Y); +}); + +y = 0; + +RIGHT_COLUMN_FONTS.forEach( function (font) { + context.font = font; + context.fillText(font, RIGHT_COLUMN_X, y += DELTA_Y); +}); + diff --git a/canvas/ch03/example-3.4/example.html b/canvas/ch03/example-3.4/example.html new file mode 100644 index 0000000..fbc7738 --- /dev/null +++ b/canvas/ch03/example-3.4/example.html @@ -0,0 +1,63 @@ + + + + + Positioning Text: textAlign/textBaseline + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch03/example-3.4/example.js b/canvas/ch03/example-3.4/example.js new file mode 100644 index 0000000..2ff6622 --- /dev/null +++ b/canvas/ch03/example-3.4/example.js @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + fontHeight = 24, + alignValues = ['start', 'center', 'end'], + baselineValues = ['top', 'middle', 'bottom', + 'alphabetic', 'ideographic', 'hanging'], + x, y; + +// Functions.......................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function drawTextMarker() { + context.fillStyle = 'yellow'; + context.fillRect (x, y, 7, 7); + context.strokeRect(x, y, 7, 7); +} + +function drawText(text, textAlign, textBaseline) { + if(textAlign) context.textAlign = textAlign; + if(textBaseline) context.textBaseline = textBaseline; + + context.fillStyle = 'cornflowerblue'; + context.fillText(text, x, y); +} + +function drawTextLine() { + context.strokeStyle = 'gray'; + + context.beginPath(); + context.moveTo(x, y); + context.lineTo(x + 738, y); + context.stroke(); +} + +// Initialization..................................................... + +context.font = 'oblique normal bold 24px palatino'; + +drawGrid('lightgray', 10, 10); + +for (var align=0; align < alignValues.length; ++align) { + for (var baseline=0; baseline < baselineValues.length; ++baseline) { + x = 20 + align*fontHeight*15; + y = 20 + baseline*fontHeight*3; + + drawText(alignValues[align] + '/' + baselineValues[baseline], + alignValues[align], baselineValues[baseline]); + + drawTextMarker(); + drawTextLine(); + } +} diff --git a/canvas/ch03/example-3.5/example.html b/canvas/ch03/example-3.5/example.html new file mode 100644 index 0000000..51fb89b --- /dev/null +++ b/canvas/ch03/example-3.5/example.html @@ -0,0 +1,60 @@ + + + + + Positioning Text + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch03/example-3.5/example.js b/canvas/ch03/example-3.5/example.js new file mode 100644 index 0000000..3c282bd --- /dev/null +++ b/canvas/ch03/example-3.5/example.js @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + text = 'Centered', + textMetrics, + SQUARE_WIDTH = 20, + FONT_HEIGHT = 128; + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +}; + +function drawText() { + context.fillStyle = 'orange'; + context.strokeStyle = 'cornflowerblue'; + + context.fillText(text, canvas.width/2, + canvas.height/2); + + context.strokeText(text, canvas.width/2, + canvas.height/2); +}; + +function drawCenterSquare() { + context.fillStyle = 'rgba(255, 0, 0, 0.4)'; + context.strokeStyle = 'black'; + context.fillRect(canvas.width/2 - SQUARE_WIDTH/2, + canvas.height/2 - SQUARE_WIDTH/2, 20, 20); + + context.strokeRect(canvas.width/2 - SQUARE_WIDTH/2, + canvas.height/2 - SQUARE_WIDTH/2, 20, 20); +}; + +context.font = '128px Helvetica'; +context.textBaseline = 'middle'; +context.textAlign = 'center'; +textMetrics = context.measureText(text); + +drawGrid('lightgray', 10, 10); +drawText(); +drawCenterSquare(); diff --git a/canvas/ch03/example-3.7/example.html b/canvas/ch03/example-3.7/example.html new file mode 100644 index 0000000..f96ebb7 --- /dev/null +++ b/canvas/ch03/example-3.7/example.html @@ -0,0 +1,61 @@ + + + + + + Drawing Axis Labels + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch03/example-3.7/example.js b/canvas/ch03/example-3.7/example.js new file mode 100644 index 0000000..a8a7d2b --- /dev/null +++ b/canvas/ch03/example-3.7/example.js @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + HORIZONTAL_AXIS_MARGIN = 50, + VERTICAL_AXIS_MARGIN = 50, + + AXIS_ORIGIN = { x: HORIZONTAL_AXIS_MARGIN, + y: canvas.height-VERTICAL_AXIS_MARGIN }, + + AXIS_TOP = VERTICAL_AXIS_MARGIN, + AXIS_RIGHT = canvas.width-HORIZONTAL_AXIS_MARGIN, + + HORIZONTAL_TICK_SPACING = 10, + VERTICAL_TICK_SPACING = 10, + + AXIS_WIDTH = AXIS_RIGHT - AXIS_ORIGIN.x, + AXIS_HEIGHT = AXIS_ORIGIN.y - AXIS_TOP, + + NUM_VERTICAL_TICKS = AXIS_HEIGHT / VERTICAL_TICK_SPACING, + NUM_HORIZONTAL_TICKS = AXIS_WIDTH / HORIZONTAL_TICK_SPACING, + + TICK_WIDTH = 10, + + SPACE_BETWEEN_LABELS_AND_AXIS = 20; + +// Functions.......................................................... + +function drawAxes() { + context.save(); + context.lineWidth = 1.0; + context.fillStyle = 'rgba(100, 140, 230, 0.8)'; + context.strokeStyle = 'navy'; + + drawHorizontalAxis(); + drawVerticalAxis(); + + context.lineWidth = 0.5; + context.strokeStyle = 'navy'; + + context.strokeStyle = 'darkred'; + drawVerticalAxisTicks(); + drawHorizontalAxisTicks(); + + context.restore(); +} + +function drawVerticalAxisTicks() { + var deltaY; + + for (var i=1; i < NUM_VERTICAL_TICKS; ++i) { + context.beginPath(); + + if (i % 5 === 0) deltaX = TICK_WIDTH; + else deltaX = TICK_WIDTH/2; + + context.moveTo(AXIS_ORIGIN.x - deltaX, + AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING); + + context.lineTo(AXIS_ORIGIN.x + deltaX, + AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING); + + context.stroke(); + } +} + +function drawHorizontalAxisTicks() { + var deltaY; + + for (var i=1; i < NUM_HORIZONTAL_TICKS; ++i) { + context.beginPath(); + + if (i % 5 === 0) deltaY = TICK_WIDTH; + else deltaY = TICK_WIDTH/2; + + context.moveTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, + AXIS_ORIGIN.y - deltaY); + + context.lineTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, + AXIS_ORIGIN.y + deltaY); + + context.stroke(); + } +} + +function drawHorizontalAxis() { + context.beginPath(); + context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y); + context.lineTo(AXIS_RIGHT, AXIS_ORIGIN.y) + context.stroke(); +} + +function drawVerticalAxis() { + context.beginPath(); + context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y); + context.lineTo(AXIS_ORIGIN.x, AXIS_TOP); + context.stroke(); +} + +function drawAxisLabels() { + context.fillStyle = 'blue'; + drawHorizontalAxisLabels(); + drawVerticalAxisLabels(); +} + +function drawHorizontalAxisLabels() { + context.textAlign = 'center'; + context.textBaseline = 'top'; + + for (var i=0; i <= NUM_HORIZONTAL_TICKS; ++i) { + if (i % 5 === 0) { + context.fillText(i, + AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, + AXIS_ORIGIN.y + SPACE_BETWEEN_LABELS_AND_AXIS); + } + } +} + +function drawVerticalAxisLabels() { + context.textAlign = 'right'; + context.textBaseline = 'middle'; + + for (var i=0; i <= NUM_VERTICAL_TICKS; ++i) { + if (i % 5 === 0) { + context.fillText(i, + AXIS_ORIGIN.x - SPACE_BETWEEN_LABELS_AND_AXIS, + AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING); + } + } +} + +function drawGrid(color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +// Initialization..................................................... + +context.font = '13px Arial'; + +drawGrid('lightgray', 10, 10); + +context.shadowColor = 'rgba(100, 140, 230, 0.8)'; +context.shadowOffsetX = 3; +context.shadowOffsetY = 3; +context.shadowBlur = 5; + +drawAxes(); +drawAxisLabels(); diff --git a/canvas/ch03/example-3.8/example.html b/canvas/ch03/example-3.8/example.html new file mode 100644 index 0000000..e0865d7 --- /dev/null +++ b/canvas/ch03/example-3.8/example.html @@ -0,0 +1,62 @@ + + + + + + A Dial Showing the Degrees of a Circle + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch03/example-3.8/example.js b/canvas/ch03/example-3.8/example.js new file mode 100644 index 0000000..697ad30 --- /dev/null +++ b/canvas/ch03/example-3.8/example.js @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + CENTROID_RADIUS = 10, + CENTROID_STROKE_STYLE = 'rgba(0, 0, 0, 0.5)', + CENTROID_FILL_STYLE = 'rgba(80, 190, 240, 0.6)', + + RING_INNER_RADIUS = 35, + RING_OUTER_RADIUS = 55, + + ANNOTATIONS_FILL_STYLE = 'rgba(0, 0, 230, 0.9)', + ANNOTATIONS_TEXT_SIZE = 12, + + TICK_WIDTH = 10, + TICK_LONG_STROKE_STYLE = 'rgba(100, 140, 230, 0.9)', + TICK_SHORT_STROKE_STYLE = 'rgba(100, 140, 230, 0.7)', + + TRACKING_DIAL_STROKING_STYLE = 'rgba(100, 140, 230, 0.5)', + + GUIDEWIRE_STROKE_STYLE = 'goldenrod', + GUIDEWIRE_FILL_STYLE = 'rgba(250, 250, 0, 0.6)', + + circle = { x: canvas.width/2, + y: canvas.height/2, + radius: 150 + }; + +// Functions.......................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, + context.canvas.height); + + for (var i = stepx + 0.5; + i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; + i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function drawDial() { + var loc = {x: circle.x, y: circle.y}; + + drawCentroid(); + drawCentroidGuidewire(loc); + + drawRing(); + drawTickInnerCircle(); + drawTicks(); + drawAnnotations(); +} + +function drawCentroid() { + context.beginPath(); + context.save(); + context.strokeStyle = CENTROID_STROKE_STYLE; + context.fillStyle = CENTROID_FILL_STYLE; + context.arc(circle.x, circle.y, + CENTROID_RADIUS, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + context.restore(); +} + +function drawCentroidGuidewire(loc) { + var angle = -Math.PI/4, + radius, endpt; + + radius = circle.radius + RING_OUTER_RADIUS; + + if (loc.x >= circle.x) { + endpt = { x: circle.x + radius * Math.cos(angle), + y: circle.y + radius * Math.sin(angle) + }; + } + else { + endpt = { x: circle.x - radius * Math.cos(angle), + y: circle.y - radius * Math.sin(angle) + }; + } + + context.save(); + + context.strokeStyle = GUIDEWIRE_STROKE_STYLE; + context.fillStyle = GUIDEWIRE_FILL_STYLE; + + context.beginPath(); + context.moveTo(circle.x, circle.y); + context.lineTo(endpt.x, endpt.y); + context.stroke(); + + context.beginPath(); + context.strokeStyle = TICK_LONG_STROKE_STYLE; + context.arc(endpt.x, endpt.y, 5, 0, Math.PI*2, false); + context.fill(); + context.stroke(); + + context.restore(); +} + +function drawRing() { + drawRingOuterCircle(); + + context.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + context.arc(circle.x, circle.y, + circle.radius + RING_INNER_RADIUS, + 0, Math.PI*2, false); + + context.fillStyle = 'rgba(100, 140, 230, 0.1)'; + context.fill(); + context.stroke(); +} + +function drawRingOuterCircle() { + context.shadowColor = 'rgba(0, 0, 0, 0.7)'; + context.shadowOffsetX = 3, + context.shadowOffsetY = 3, + context.shadowBlur = 6, + context.strokeStyle = TRACKING_DIAL_STROKING_STYLE; + context.beginPath(); + context.arc(circle.x, circle.y, circle.radius + + RING_OUTER_RADIUS, 0, Math.PI*2, true); + context.stroke(); +} + +function drawTickInnerCircle() { + context.save(); + context.beginPath(); + context.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + context.arc(circle.x, circle.y, + circle.radius + RING_INNER_RADIUS - TICK_WIDTH, + 0, Math.PI*2, false); + context.stroke(); + context.restore(); +} + +function drawTick(angle, radius, cnt) { + var tickWidth = cnt % 4 === 0 ? TICK_WIDTH : TICK_WIDTH/2; + + context.beginPath(); + + context.moveTo(circle.x + Math.cos(angle) * (radius - tickWidth), + circle.y + Math.sin(angle) * (radius - tickWidth)); + + context.lineTo(circle.x + Math.cos(angle) * (radius), + circle.y + Math.sin(angle) * (radius)); + + context.strokeStyle = TICK_SHORT_STROKE_STYLE; + context.stroke(); +} + +function drawTicks() { + var radius = circle.radius + RING_INNER_RADIUS, + ANGLE_MAX = 2*Math.PI, + ANGLE_DELTA = Math.PI/64, + tickWidth; + + context.save(); + + for (var angle = 0, cnt = 0; angle < ANGLE_MAX; + angle += ANGLE_DELTA, cnt++) { + drawTick(angle, radius, cnt++); + } + + context.restore(); +} + +function drawAnnotations() { + var radius = circle.radius + RING_INNER_RADIUS; + + context.save(); + context.fillStyle = ANNOTATIONS_FILL_STYLE; + context.font = ANNOTATIONS_TEXT_SIZE + 'px Helvetica'; + + for (var angle=0; angle < 2*Math.PI; angle += Math.PI/8) { + context.beginPath(); + context.fillText((angle * 180 / Math.PI).toFixed(0), + circle.x + Math.cos(angle) * (radius - TICK_WIDTH*2), + circle.y - Math.sin(angle) * (radius - TICK_WIDTH*2)); + } + context.restore(); +} + +// Initialization.................................................... + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(0, 0, 0, 0.4)'; + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; + +context.textAlign = 'center'; +context.textBaseline = 'middle'; + +drawGrid('lightgray', 10, 10); +drawDial(); diff --git a/canvas/ch03/example-3.8/polygon.js b/canvas/ch03/example-3.8/polygon.js new file mode 100644 index 0000000..86d3ac6 --- /dev/null +++ b/canvas/ch03/example-3.8/polygon.js @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var Point = function (x, y) { + this.x = x; + this.y = y; +}; + +var Polygon = function (centerX, centerY, radius, sides, startAngle, strokeStyle, fillStyle, filled) { + this.x = centerX; + this.y = centerY; + this.radius = radius; + this.sides = sides; + this.startAngle = startAngle; + this.strokeStyle = strokeStyle; + this.fillStyle = fillStyle; + this.filled = filled; +}; + +Polygon.prototype = { + getPoints: function () { + var points = [], + angle = this.startAngle || 0; + + for (var i=0; i < this.sides; ++i) { + points.push(new Point(this.x + this.radius * Math.sin(angle), + this.y - this.radius * Math.cos(angle))); + angle += 2*Math.PI/this.sides; + } + return points; + }, + + createPath: function (context) { + var points = this.getPoints(); + + context.beginPath(); + + context.moveTo(points[0].x, points[0].y); + + for (var i=1; i < this.sides; ++i) { + context.lineTo(points[i].x, points[i].y); + } + + context.closePath(); + }, + + stroke: function (context) { + context.save(); + this.createPath(context); + context.strokeStyle = this.strokeStyle; + context.stroke(); + context.restore(); + }, + + fill: function (context) { + context.save(); + this.createPath(context); + context.fillStyle = this.fillStyle; + context.fill(); + context.restore(); + }, + + move: function (x, y) { + this.x = x; + this.y = y; + }, +}; diff --git a/canvas/ch03/example-3.9/example.html b/canvas/ch03/example-3.9/example.html new file mode 100644 index 0000000..84745d8 --- /dev/null +++ b/canvas/ch03/example-3.9/example.html @@ -0,0 +1,62 @@ + + + + + + Drawing Circular Text + + + + + + + Canvas not supported + + + + + diff --git a/canvas/ch03/example-3.9/example.js b/canvas/ch03/example-3.9/example.js new file mode 100644 index 0000000..7d5bf8e --- /dev/null +++ b/canvas/ch03/example-3.9/example.js @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + CENTROID_RADIUS = 10, + CENTROID_STROKE_STYLE = 'rgba(0, 0, 0, 0.5)', + CENTROID_FILL_STYLE ='rgba(80, 190, 240, 0.6)', + + GUIDEWIRE_STROKE_STYLE = 'goldenrod', + GUIDEWIRE_FILL_STYLE = 'rgba(85, 190, 240, 0.6)', + + TEXT_FILL_STYLE = 'rgba(100, 130, 240, 0.5)', + TEXT_STROKE_STYLE = 'rgba(200, 0, 0, 0.7)', + TEXT_SIZE = 64, + + GUIDEWIRE_STROKE_STYLE = 'goldenrod', + GUIDEWIRE_FILL_STYLE = 'rgba(85, 190, 240, 0.6)', + + circle = { x: canvas.width/2, + y: canvas.height/2, + radius: 200 + }; + +// Functions..................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +// Drawing functions............................................. + +function drawCentroid() { + context.beginPath(); + context.save(); + context.strokeStyle = CENTROID_STROKE_STYLE; + context.fillStyle = CENTROID_FILL_STYLE; + context.arc(circle.x, circle.y, CENTROID_RADIUS, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + context.restore(); +} + +function drawCircularText(string, startAngle, endAngle) { + var radius = circle.radius, + angleDecrement = (startAngle - endAngle)/(string.length-1), + angle = parseFloat(startAngle), + index = 0, + character; + + context.save(); + context.fillStyle = TEXT_FILL_STYLE; + context.strokeStyle = TEXT_STROKE_STYLE; + context.font = TEXT_SIZE + 'px Lucida Sans'; + + while (index < string.length) { + character = string.charAt(index); + + context.save(); + context.beginPath(); + + context.translate( + circle.x + Math.cos(angle) * radius, + circle.y - Math.sin(angle) * radius); + + context.rotate(Math.PI/2 - angle); + + context.fillText(character, 0, 0); + context.strokeText(character, 0, 0); + + angle -= angleDecrement; + index++; + + context.restore(); + } + context.restore(); +} + +// Initialization................................................ + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(0, 0, 0, 0.4)'; + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 5; + +context.textAlign = 'center'; +context.textBaseline = 'middle'; + +drawGrid('lightgray', 10, 10); +drawCentroid(); + +drawCircularText("Clockwise around the circle", Math.PI*2, Math.PI/8); diff --git a/canvas/ch04/example-4.1/countrypath.jpg b/canvas/ch04/example-4.1/countrypath.jpg new file mode 100644 index 0000000..c2e5a76 Binary files /dev/null and b/canvas/ch04/example-4.1/countrypath.jpg differ diff --git a/canvas/ch04/example-4.1/example.html b/canvas/ch04/example-4.1/example.html new file mode 100644 index 0000000..8bdd6b8 --- /dev/null +++ b/canvas/ch04/example-4.1/example.html @@ -0,0 +1,60 @@ + + + + + + Drawing Images into a Canvas + + + + + + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.1/example.js b/canvas/ch04/example-4.1/example.js new file mode 100644 index 0000000..d9152f6 --- /dev/null +++ b/canvas/ch04/example-4.1/example.js @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + image = new Image(); + +image.src = '../../shared/images/countrypath.jpg'; +image.onload = function(e) { + context.drawImage(image, 0, 0); +}; diff --git a/canvas/ch04/example-4.12/example.html b/canvas/ch04/example-4.12/example.html new file mode 100644 index 0000000..f1384f3 --- /dev/null +++ b/canvas/ch04/example-4.12/example.html @@ -0,0 +1,69 @@ + + + + + + + Rubberbands with getImageData() and putImageData() + + + + + + +
+ +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.12/example.js b/canvas/ch04/example-4.12/example.js new file mode 100644 index 0000000..1ea4f16 --- /dev/null +++ b/canvas/ch04/example-4.12/example.js @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + resetButton = document.getElementById('resetButton'), + + image = new Image(), + imageData, + imageDataCopy = context.createImageData(canvas.width, canvas.height), + + mousedown = {}, + rubberbandRectangle = {}, + dragging = false; + +// Functions..................................................... + +function windowToCanvas(canvas, x, y) { + var canvasRectangle = canvas.getBoundingClientRect(); + + return { + x: x - canvasRectangle.left, + y: y - canvasRectangle.top + }; +} + +function copyCanvasPixels() { + var i=0; + + // Copy red, green, and blue components of the first pixel + + for (i=0; i < 3; i++) { + imageDataCopy.data[i] = imageData.data[i]; + } + + // Starting with the alpha component of the first pixel, + // copy imageData, and make the copy more transparent + + for (i=3; i < imageData.data.length - 4; i+=4) { + imageDataCopy.data[i] = imageData.data[i] / 2; // Alpha: more transparent + imageDataCopy.data[i+1] = imageData.data[i+1]; // Red + imageDataCopy.data[i+2] = imageData.data[i+2]; // Green + imageDataCopy.data[i+3] = imageData.data[i+3]; // Blue + } +} + +function captureCanvasPixels() { + imageData = context.getImageData(0, 0, canvas.width, canvas.height); + copyCanvasPixels(); +} + +function restoreRubberbandPixels() { + var deviceWidthOverCSSPixels = imageData.width / canvas.width, + deviceHeightOverCSSPixels = imageData.height / canvas.height; + + // Restore the Canvas to what it looked like when the mouse went down + + context.putImageData(imageData, 0, 0); + + // Put the more transparent image data into the rubberband rectangle + + context.putImageData(imageDataCopy, 0, 0, + (rubberbandRectangle.left + context.lineWidth), + (rubberbandRectangle.top + context.lineWidth), + (rubberbandRectangle.width - 2*context.lineWidth) * deviceWidthOverCSSPixels, + (rubberbandRectangle.height - 2*context.lineWidth) * deviceHeightOverCSSPixels); +} + +function setRubberbandRectangle(x, y) { + rubberbandRectangle.left = Math.min(x, mousedown.x); + rubberbandRectangle.top = Math.min(y, mousedown.y); + rubberbandRectangle.width = Math.abs(x - mousedown.x), + rubberbandRectangle.height = Math.abs(y - mousedown.y); +} + +function drawRubberband() { + var deviceWidthOverCSSPixels = imageData.width / canvas.width, + deviceHeightOverCSSPixels = imageData.height / canvas.height; + + context.strokeRect(rubberbandRectangle.left + context.lineWidth, + rubberbandRectangle.top + context.lineWidth, + rubberbandRectangle.width - 2*context.lineWidth, + rubberbandRectangle.height - 2*context.lineWidth); +} + +function rubberbandStart(x, y) { + mousedown.x = x; + mousedown.y = y; + + rubberbandRectangle.left = mousedown.x; + rubberbandRectangle.top = mousedown.y; + rubberbandRectangle.width = 0; + rubberbandRectangle.height = 0; + + dragging = true; + + captureCanvasPixels(); +} + +function rubberbandStretch(x, y) { + if (rubberbandRectangle.width > 2*context.lineWidth && + rubberbandRectangle.height > 2*context.lineWidth) { + if (imageData !== undefined) { + restoreRubberbandPixels(); + } + } + + setRubberbandRectangle(x, y); + + if (rubberbandRectangle.width > 2*context.lineWidth && + rubberbandRectangle.height > 2*context.lineWidth) { + drawRubberband(); + } +}; + +function rubberbandEnd() { + context.putImageData(imageData, 0, 0); + + // Draw the canvas back into itself, scaling along the way + + context.drawImage(canvas, + rubberbandRectangle.left + context.lineWidth*2, + rubberbandRectangle.top + context.lineWidth*2, + rubberbandRectangle.width - 4*context.lineWidth, + rubberbandRectangle.height - 4*context.lineWidth, + 0, 0, canvas.width, canvas.height); + + dragging = false; + imageData = undefined; +} + +// Event handlers............................................... + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(canvas, e.clientX, e.clientY); + e.preventDefault(); + rubberbandStart(loc.x, loc.y); +}; + +canvas.onmousemove = function (e) { + var loc; + + if (dragging) { + loc = windowToCanvas(canvas, e.clientX, e.clientY); + rubberbandStretch(loc.x, loc.y); + } +} + +canvas.onmouseup = function (e) { + rubberbandEnd(); +}; + +// Initialization.............................................. + +image.src = '../../shared/images/arch.png'; +image.onload = function () { + context.drawImage(image, 0, 0, canvas.width, canvas.height); +}; + +resetButton.onclick = function(e) { + context.clearRect(0, 0, + canvas.width, canvas.height); + + context.drawImage(image, 0, 0, canvas.width, canvas.height); +}; + +context.strokeStyle = 'navy'; +context.lineWidth = 1.0; diff --git a/canvas/ch04/example-4.13/example.html b/canvas/ch04/example-4.13/example.html new file mode 100644 index 0000000..3baed5f --- /dev/null +++ b/canvas/ch04/example-4.13/example.html @@ -0,0 +1,73 @@ + + + + + + A negative image filter + + + + + +
+ +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.13/example.js b/canvas/ch04/example-4.13/example.js new file mode 100644 index 0000000..96daac6 --- /dev/null +++ b/canvas/ch04/example-4.13/example.js @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var image = new Image(), + canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + negativeButton = document.getElementById('negativeButton'); + +negativeButton.onclick = function() { + var imagedata = context.getImageData(0, 0, canvas.width, canvas.height), + data = imagedata.data; + + for(i=0; i <= data.length - 4; i+=4) { + data[i] = 255 - data[i] + data[i+1] = 255 - data[i+1]; + data[i+2] = 255 - data[i+2]; + } + context.putImageData(imagedata, 0, 0); +}; + +image.src = '../../shared/images/curved-road.png'; +image.onload = function() { + context.drawImage(image, 0, 0, + image.width, image.height, 0, 0, + context.canvas.width, context.canvas.height); +}; diff --git a/canvas/ch04/example-4.14/example.html b/canvas/ch04/example-4.14/example.html new file mode 100644 index 0000000..6281a9d --- /dev/null +++ b/canvas/ch04/example-4.14/example.html @@ -0,0 +1,76 @@ + + + + + + A black and white image filter + + + + + +
+ + Color +
+ + + Canvas not supported + + + + + diff --git a/canvas/ch04/example-4.14/example.js b/canvas/ch04/example-4.14/example.js new file mode 100644 index 0000000..0276a24 --- /dev/null +++ b/canvas/ch04/example-4.14/example.js @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var image = new Image(), + canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + colorToggleCheckbox = document.getElementById('colorToggleCheckbox'); + +function drawInBlackAndWhite() { + var data = undefined, + i = 0; + + imagedata = context.getImageData(0, 0, + canvas.width, canvas.height); + data = imagedata.data; + + for(i=0; i < data.length - 4; i+=4) { + average = (data[i] + data[i+1] + data[i+2]) / 3; + data[i] = average; + data[i+1] = average; + data[i+2] = average; + } + context.putImageData(imagedata, 0, 0); +} + +function drawInColor() { + context.drawImage(image, 0, 0, + image.width, image.height, 0, 0, + context.canvas.width, context.canvas.height); +} + +colorToggleCheckbox.onclick = function() { + if (colorToggleCheckbox.checked) { + drawInColor(); + } + else { + drawInBlackAndWhite(); + } +}; + +image.src = '../../shared/images/curved-road.png'; +image.onload = function() { + drawInColor(); +}; diff --git a/canvas/ch04/example-4.15/example.html b/canvas/ch04/example-4.15/example.html new file mode 100644 index 0000000..9ef69a5 --- /dev/null +++ b/canvas/ch04/example-4.15/example.html @@ -0,0 +1,73 @@ + + + + + + Embossing Filter + + + + + +
+ +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.15/example.js b/canvas/ch04/example-4.15/example.js new file mode 100644 index 0000000..2246ae8 --- /dev/null +++ b/canvas/ch04/example-4.15/example.js @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var image = new Image(), + canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + embossButton = document.getElementById('embossButton'), + embossed = false; + +// Functions..................................................... + +function emboss() { + var imagedata, data, length, width, index=3; + + imagedata = context.getImageData(0, 0, + canvas.width, canvas.height); + data = imagedata.data; + width = imagedata.width; + length = data.length; + + for (i=0; i < length; i++) { // loop through every pixel + + // if we won't overrun the bounds of the array + if (i <= length-width*4) { + + // if it's not an alpha + if ((i+1) % 4 !== 0) { + + // if it's the last pixel in the row, there is + // no pixel to the right, so copy previous pixel's + // values. + + if ((i+4) % (width*4) == 0) { + data[i] = data[i-4]; + data[i+1] = data[i-3]; + data[i+2] = data[i-2]; + data[i+3] = data[i-1]; + i+=4; + } + else { // not the last pixel in the row + data[i] = 255/2 // Average value + + 2*data[i] // current pixel + - data[i+4] // next pixel + - data[i+width*4]; // pixel underneath + } + } + } + else { // last row, no pixels underneath, + // so copy pixel above + if ((i+1) % 4 !== 0) { + data[i] = data[i-width*4]; + } + } + } + context.putImageData(imagedata, 0, 0); +} + +function drawOriginalImage() { + context.drawImage(image, 0, 0, + image.width, image.height, + 0, 0, canvas.width, canvas.height); +} + +embossButton.onclick = function() { + if (embossed) { + embossButton.value = 'Emboss'; + drawOriginalImage(); + embossed = false; + } + else { + embossButton.value = 'Original image'; + emboss(); + embossed = true; + } +}; + +// Initialization................................................ + +image.src = '../../shared/images/curved-road.png'; +image.onload = function() { + drawOriginalImage(); +}; diff --git a/canvas/ch04/example-4.16/example.html b/canvas/ch04/example-4.16/example.html new file mode 100644 index 0000000..b46d481 --- /dev/null +++ b/canvas/ch04/example-4.16/example.html @@ -0,0 +1,73 @@ + + + + + + A sunglass image filter with Workers + + + + + +
+ +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.16/example.js b/canvas/ch04/example-4.16/example.js new file mode 100644 index 0000000..e782015 --- /dev/null +++ b/canvas/ch04/example-4.16/example.js @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var image = new Image(), + canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + sunglassButton = document.getElementById('sunglassButton'), + sunglassesOn = false, + sunglassFilter = new Worker('sunglassFilter.js'); + +function putSunglassesOn() { + sunglassFilter.postMessage( + context.getImageData(0, 0, canvas.width, canvas.height)); + + sunglassFilter.onmessage = function (event) { + context.putImageData(event.data, 0, 0); + }; +} + +function drawOriginalImage() { + context.drawImage(image, 0, 0, + image.width, image.height, 0, 0, + canvas.width, canvas.height); +} + +sunglassButton.onclick = function() { + if (sunglassesOn) { + sunglassButton.value = 'Sunglasses'; + drawOriginalImage(); + sunglassesOn = false; + } + else { + sunglassButton.value = 'Original picture'; + putSunglassesOn(); + sunglassesOn = true; + } +}; + +image.src = '../../shared/images/curved-road.png'; +image.onload = function() { + drawOriginalImage(); +}; diff --git a/canvas/ch04/example-4.16/sunglassFilter.js b/canvas/ch04/example-4.16/sunglassFilter.js new file mode 100644 index 0000000..abb8442 --- /dev/null +++ b/canvas/ch04/example-4.16/sunglassFilter.js @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +onmessage = function (event) { + var imagedata = event.data, + data = imagedata.data, + length = data.length, + width = imagedata.width; + + for (i=0; i < length; ++i) { + if ((i+1) % 4 != 0) { + if ((i+4) % (width*4) == 0) { // last pixel in a row + data[i] = data[i-4]; + data[i+1] = data[i-3]; + data[i+2] = data[i-2]; + data[i+3] = data[i-1]; + i+=4; + } + else { + data[i] = 2*data[i] - data[i+4] - 0.5*data[i+4]; + } + } + } + + postMessage(imagedata); +}; diff --git a/canvas/ch04/example-4.18/example.html b/canvas/ch04/example-4.18/example.html new file mode 100644 index 0000000..87fc257 --- /dev/null +++ b/canvas/ch04/example-4.18/example.html @@ -0,0 +1,73 @@ + + + + + + Sunglasses + + + + + +
+ +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.18/example.js b/canvas/ch04/example-4.18/example.js new file mode 100644 index 0000000..8880089 --- /dev/null +++ b/canvas/ch04/example-4.18/example.js @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var image = new Image(), + canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + offscreenCanvas = document.createElement('canvas'), + offscreenContext = offscreenCanvas.getContext('2d'), + sunglassButton = document.getElementById('sunglassButton'), + sunglassesOn = false, + sunglassFilter = new Worker('sunglassFilter.js'), + LENS_RADIUS = canvas.width/5; + +function drawLenses(leftLensLocation, rightLensLocation) { + context.save(); + context.beginPath(); + + context.arc(leftLensLocation.x, leftLensLocation.y, + LENS_RADIUS, 0, Math.PI*2,false); + context.stroke(); + + moveTo(rightLensLocation.x, rightLensLocation.y); + + context.arc(rightLensLocation.x, rightLensLocation.y, + LENS_RADIUS, 0, Math.PI*2,false); + context.stroke(); + + context.clip(); + + context.drawImage(offscreenCanvas,0,0, + canvas.width,canvas.height); + context.restore(); +} + +function drawWire(center) { + context.beginPath(); + context.moveTo(center.x - LENS_RADIUS/4, + center.y - LENS_RADIUS/2); + + context.quadraticCurveTo(center.x, center.y - LENS_RADIUS+20, + center.x + LENS_RADIUS/4, + center.y - LENS_RADIUS/2); + context.stroke(); +} + +function drawConnectors(center) { + context.beginPath(); + + context.fillStyle = 'silver'; + context.strokeStyle = 'rgba(0,0,0,0.4)'; + context.lineWidth = 2; + + context.arc(center.x - LENS_RADIUS/4, + center.y - LENS_RADIUS/2, + 4, 0, Math.PI*2, false); + context.fill(); + context.stroke(); + + context.beginPath(); + context.arc(center.x + LENS_RADIUS/4, center.y - LENS_RADIUS/2, + 4, 0, Math.PI*2, false); + context.fill(); + context.stroke(); +} + +function putSunglassesOn() { + var imagedata, + center = { + x: canvas.width/2, + y: canvas.height/2 + }, + leftLensLocation = { + x: center.x - LENS_RADIUS - 10, + y: center.y + }, + rightLensLocation = { + x: center.x + LENS_RADIUS + 10, + y: center.y + }, + + imagedata = context.getImageData(0, 0, + canvas.width, canvas.height); + + sunglassFilter.postMessage(imagedata); + + sunglassFilter.onmessage = function(event) { + offscreenContext.putImageData(event.data, 0, 0); + drawLenses(leftLensLocation, rightLensLocation); + drawWire(center); + drawConnectors(center); + }; +} + +function drawOriginalImage() { + context.drawImage(image, 0, 0, image.width, image.height, + 0, 0, canvas.width, canvas.height); +} + +sunglassButton.onclick = function() { + if (sunglassesOn) { + sunglassButton.value = 'Sunglasses'; + drawOriginalImage(); + sunglassesOn = false; + } + else { + sunglassButton.value = 'Original picture'; + putSunglassesOn(); + sunglassesOn = true; + } +}; + +offscreenCanvas.width = canvas.width; +offscreenCanvas.height = canvas.height; + +image.src = '../../shared/images/curved-road.png'; +image.onload = function() { + drawOriginalImage(); +}; diff --git a/canvas/ch04/example-4.18/sunglassFilter.js b/canvas/ch04/example-4.18/sunglassFilter.js new file mode 100644 index 0000000..477f429 --- /dev/null +++ b/canvas/ch04/example-4.18/sunglassFilter.js @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +onmessage = function emboss(event) { + var imagedata = event.data, + data = imagedata.data, + length = data.length, + width = imagedata.width; + + for (i=0; i < length; ++i) { + if ((i+1) % 4 != 0) { + if ((i+4) % (width*4) == 0) { // last pixel in a row + data[i] = data[i-4]; + data[i+1] = data[i-3]; + data[i+2] = data[i-2]; + data[i+3] = data[i-1]; + i+=4; + } + else { + data[i] = 2*data[i] - data[i+4] - 0.5*data[i+4]; + } + } + } + + postMessage(imagedata); +}; diff --git a/canvas/ch04/example-4.19/example.html b/canvas/ch04/example-4.19/example.html new file mode 100644 index 0000000..63d9e92 --- /dev/null +++ b/canvas/ch04/example-4.19/example.html @@ -0,0 +1,73 @@ + + + + + + Animation: Fade Out + + + + + +
+ +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.19/example.js b/canvas/ch04/example-4.19/example.js new file mode 100644 index 0000000..a3e4ca0 --- /dev/null +++ b/canvas/ch04/example-4.19/example.js @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var image = new Image(), + canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + fadeButton = document.getElementById('fadeButton'), + originalImageData = null, + interval = null; + +// Functions..................................................... + +function increaseTransparency(imagedata, steps) { + var alpha, currentAlpha, step, length = imagedata.data.length; + + for (var i=3; i < length; i+=4) { // For every alpha component + alpha = originalImageData.data[i]; + + if (alpha > 0 && imagedata.data[i] > 0) { // not totally transparent yet + currentAlpha = imagedata.data[i]; + step = Math.ceil(alpha/steps); + + if (currentAlpha - step > 0) { // not too close to the end + imagedata.data[i] -= step; // increase transparency + } + else { + imagedata.data[i] = 0; // end: totally transparent + } + } + } +} + +function fadeOut(context, imagedata, x, y, + steps, millisecondsPerStep) { + var frame = 0, + length = imagedata.data.length; + + interval = setInterval(function () { // Once every millisecondsPerStep ms + frame++; + + if (frame > steps) { // animation is over + clearInterval(interval); // end animation + animationComplete(); // put picture back in 1s + } + else { + increaseTransparency(imagedata, steps); + context.putImageData(imagedata, x, y); + } + }, millisecondsPerStep); +}; + +// Animation..................................................... + +function animationComplete() { + setTimeout(function() { + context.drawImage(image,0,0,canvas.width,canvas.height); + }, 1000); +} + +// Initialization................................................ + +fadeButton.onclick = function() { + fadeOut(context, + context.getImageData(0, 0, canvas.width, canvas.height), + 0, 0, 20, 1000/60); +}; + +image.src = '../../shared/images/log-crossing.png'; +image.onload = function() { + context.drawImage(image, 0, 0, canvas.width, canvas.height); + originalImageData = context.getImageData(0, 0, canvas.width, canvas.height); +}; + diff --git a/canvas/ch04/example-4.2/example.html b/canvas/ch04/example-4.2/example.html new file mode 100644 index 0000000..a1c7a8c --- /dev/null +++ b/canvas/ch04/example-4.2/example.html @@ -0,0 +1,68 @@ + + + + + + Scaling Images + + + + + +
+ + Scale image to canvas +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.2/example.js b/canvas/ch04/example-4.2/example.js new file mode 100644 index 0000000..4a2d299 --- /dev/null +++ b/canvas/ch04/example-4.2/example.js @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + image = new Image(), + scaleCheckbox = document.getElementById('scaleCheckbox'); + +// Functions..................................................... + +function drawImage() { + context.clearRect(0,0,canvas.width,canvas.height); + + if (scaleCheckbox.checked) { + context.drawImage(image, 0, 0, canvas.width, canvas.height); + } + else { + context.drawImage(image, 0, 0); + } + context.restore(); +} + +// Event Handlers................................................ + +scaleCheckbox.onchange = function(e) { + drawImage(); +} + +// Initialization................................................ + +image.src = '../../shared/images/waterfall.png'; +image.onload = function(e) { + drawImage(); +}; diff --git a/canvas/ch04/example-4.20/example.html b/canvas/ch04/example-4.20/example.html new file mode 100644 index 0000000..3e2e577 --- /dev/null +++ b/canvas/ch04/example-4.20/example.html @@ -0,0 +1,73 @@ + + + + + + Animation: Fade In + + + + + +
+ +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.20/example.js b/canvas/ch04/example-4.20/example.js new file mode 100644 index 0000000..d918625 --- /dev/null +++ b/canvas/ch04/example-4.20/example.js @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var image = new Image(), + canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + offscreenCanvas = document.createElement('canvas'), + offscreenContext = offscreenCanvas.getContext('2d'), + fadeButton = document.getElementById('fadeButton'), + imagedata, + imagedataOffscreen, + interval = null; + +// Functions..................................................... + +function increaseTransparency(imagedata, steps) { + var alpha, + currentAlpha, + step, + length = imagedata.data.length; + + for (var i=3; i < length; i+=4) { // For every alpha component + alpha = imagedataOffscreen.data[i]; + + if (alpha > 0) { + currentAlpha = imagedata.data[i]; + step = Math.ceil(alpha/steps); + + if (currentAlpha + step <= alpha) { // Not at original alpha setting yet + imagedata.data[i] += step; // increase transparency + } + else { + imagedata.data[i] = alpha; // end: original transparency + } + } + } +} + +function fadeIn(context, imagedata, steps, millisecondsPerStep) { + var frame = 0; + + for (var i=3; i < imagedata.data.length; i+=4) { // For every alpha component + imagedata.data[i] = 0; + } + + interval = setInterval(function () { // Every millisecondsPerStep + frame++; + + if (frame > steps) { + clearInterval(interval); + //animationComplete(); + } + else { + increaseTransparency(imagedata, steps); + context.putImageData(imagedata, 0, 0); + } + }, millisecondsPerStep); + +}; + +// Animation..................................................... + +function animationComplete() { + setTimeout(function() { + context.clearRect(0,0,canvas.width,canvas.height); + }, 1000); +} + +fadeButton.onclick = function() { + imagedataOffscreen = offscreenContext.getImageData(0, 0, + canvas.width, canvas.height); + + fadeIn(context, + offscreenContext.getImageData(0, 0, + canvas.width, canvas.height), + 50, + 1000 / 60); +}; + +// Initialization................................................ + +image.src = '../../shared/images/log-crossing.png'; +image.onload = function() { + offscreenCanvas.width = canvas.width; + offscreenCanvas.height = canvas.height; + offscreenContext.drawImage(image,0,0); +}; diff --git a/canvas/ch04/example-4.22/#example.js# b/canvas/ch04/example-4.22/#example.js# new file mode 100644 index 0000000..f6b8c03 --- /dev/null +++ b/canvas/ch04/example-4.22/#example.js# @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + image = new Image(), + imageData = null, + dragging = false, + + glassSizeCanvas = document.getElementById('glassSizeCanvas'), + glassSizeContext = glassSizeCanvas.getContext('2d'), + + MAXIMUM_SCALE = 4.0, + scaleOutput = document.getElementById('scaleOutput'), + + magnifyingGlassRadius = 120, + magnificationScale = scaleOutput.innerHTML, + magnifyRectangle = {}, + + MAX_GLASS_RADIUS = 350, + + magnifyingGlassX = 512, + magnifyingGlassY = 340, + + magnifyZoomSlider = new COREHTML5.Slider('navy', + 'rgb(80, 140, 230)', + 0.25, // knob percent + 90, // take up % of width + 55), // take up % of height + + glassSlider = new COREHTML5.Slider('navy', 'rgb(80, 140, 230)', 0.50, 90, 55), + + animating = false, + animationLoop = null, + + mousedown = null, + mouseup = null, + + canvasRatio = canvas.height / canvas.width, + pinchRatio; + +// Functions................................................... + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function calculateMagnifyRectangle(mouse) { + var top, + left, + bottom, + right; + + magnifyRectangle.x = mouse.x - magnifyingGlassRadius; + magnifyRectangle.y = mouse.y - magnifyingGlassRadius; + magnifyRectangle.width = magnifyingGlassRadius*2 + 2*context.lineWidth; + magnifyRectangle.height = magnifyingGlassRadius*2 + 2*context.lineWidth; + + top = magnifyRectangle.y; + left = magnifyRectangle.x; + bottom = magnifyRectangle.y + magnifyRectangle.height; + right = magnifyRectangle.x + magnifyRectangle.width; + + if (left < 0) { + magnifyRectangle.width += left; + magnifyRectangle.x = 0; + } + else if (right > canvas.width) { + magnifyRectangle.width -= right - canvas.width; + } + + if (top < 0) { + magnifyRectangle.height += magnifyRectangle.y; + magnifyRectangle.y = 0; + } + else if (bottom > canvas.height) { + magnifyRectangle.height -= bottom - canvas.height; + } +} +/* +function drawMagnifyingGlassCircle(mouse) { + context.save(); + context.lineWidth = 3; + context.strokeStyle = 'rgba(0, 0, 255, 0.3)'; + + context.shadowColor = 'rgba(0, 0, 155, 1)'; + context.shadowOffsetX = '-10'; + context.shadowOffsetY = '-10'; + context.shadowBlur = '20'; + + context.beginPath(); + context.arc(mouse.x, mouse.y, + magnifyingGlassRadius, 0, Math.PI*2, false); + context.clip(); + context.shadowColor = 'cornflowerblue'; + context.strokeStyle = 'skyblue'; + context.stroke(); + + context.beginPath(); + context.lineWidth = 1; + context.strokeStyle = 'rgba(100, 149, 240, 0.7)'; + context.arc(mouse.x, mouse.y, + magnifyingGlassRadius-1, 0, Math.PI*2, false); + context.stroke(); + + context.beginPath(); + context.strokeStyle = 'rgba(100, 149, 240, 0.5)'; + context.lineWidth = 2; + context.arc(mouse.x, mouse.y, + magnifyingGlassRadius-1, 0, Math.PI*2, false); + context.stroke(); + + context.beginPath(); + context.strokeStyle = 'rgba(255, 255, 0, 0.3)'; + context.lineWidth = 1; + context.arc(mouse.x, mouse.y, + magnifyingGlassRadius-3, 0, Math.PI*2, false); + context.stroke(); + + context.strokeStyle = 'rgba(0, 0, 0, 0.5)'; + context.shadowColor = 'rgba(0, 0, 0, 0.8)'; + context.shadowOffsetX = '10'; + context.shadowOffsetY = '10'; + context.shadowBlur = '20'; + context.lineWidth = 4; + context.stroke(); + + context.lineWidth = 2; + context.strokeStyle = 'silver'; + context.shadowColor = 'goldenrod'; + context.stroke(); + + context.restore(); +} +*/ +function setClip() { + context.beginPath(); +/* +context.strokeStyle = 'blue'; +context.lineWidth = 0.5; +context.strokeRect(magnifyingGlassX-magnifyingGlassRadius, magnifyingGlassY-magnifyingGlassRadius, magnifyingGlassRadius*2+3, magnifyingGlassRadius*2+3); + */ + //context.beginPath(); + context.arc(magnifyingGlassX, magnifyingGlassY, + magnifyingGlassRadius, 0, Math.PI*2, false); + + context.clip(); +} + +function drawMagnifyingGlassCircle(mouse) { + var gradientThickness = this.magnifyingGlassRadius / 7; + + gradientThickness = gradientThickness < 10 ? 10 : gradientThickness; + gradientThickness = gradientThickness > 40 ? 40 : gradientThickness; + + gradientThickness = 10; + this.context.save(); + this.context.lineWidth = gradientThickness; + this.context.strokeStyle = 'rgb(0, 0, 255, 0.3)'; + + this.context.beginPath(); + this.context.arc(mouse.x, mouse.y, + this.magnifyingGlassRadius, 0, Math.PI*2, false); + this.context.clip(); + + var gradient = this.context.createRadialGradient( + mouse.x, mouse.y, this.magnifyingGlassRadius-gradientThickness, + mouse.x, mouse.y, this.magnifyingGlassRadius); + gradient.addColorStop(0, 'rgba(0,0,0,0.2)'); + gradient.addColorStop(0.80, 'rgb(235,237,255)'); + gradient.addColorStop(0.90, 'rgb(235,237,255)'); + gradient.addColorStop(1.0, 'rgba(150,150,150,0.9)'); + + this.context.shadowColor = 'rgba(52, 72, 35, 1.0)'; + this.context.shadowOffsetX = 2; + this.context.shadowOffsetY = 2; + this.context.shadowBlur = 20; + + this.context.strokeStyle = gradient; + this.context.stroke(); + + this.context.beginPath(); + this.context.arc(mouse.x, mouse.y, + this.magnifyingGlassRadius-gradientThickness/2, 0, Math.PI*2, false); + this.context.clip(); + + this.context.lineWidth = gradientThickness; + this.context.strokeStyle = 'rgba(0,0,0,0.06)'; + this.context.stroke(); + + this.context.restore(); +}; + +function drawMagnifyingGlass(mouse) { + var scaledMagnifyRectangle; + + if (window.netscape && netscape.security.PrivilegeManager) + netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); + + magnifyingGlassX = mouse.x; + magnifyingGlassY = mouse.y; + + calculateMagnifyRectangle(mouse); + + imageData = context.getImageData(magnifyRectangle.x, + magnifyRectangle.y, + magnifyRectangle.width, + magnifyRectangle.height); + context.save(); + + scaledMagnifyRectangle = { + width: magnifyRectangle.width * magnificationScale, + height: magnifyRectangle.height * magnificationScale + }; + + setClip(); + + context.drawImage(canvas, + magnifyRectangle.x, magnifyRectangle.y, + magnifyRectangle.width, magnifyRectangle.height, + + magnifyRectangle.x + magnifyRectangle.width/2 - + scaledMagnifyRectangle.width/2, + + magnifyRectangle.y + magnifyRectangle.height/2 - + scaledMagnifyRectangle.height/2, + + scaledMagnifyRectangle.width, + scaledMagnifyRectangle.height); + + context.restore(); + + drawMagnifyingGlassCircle(mouse); +} + +function eraseMagnifyingGlass() { // Called when the mouse moves + if (imageData != null) { + context.putImageData(imageData, + magnifyRectangle.x, + magnifyRectangle.y); + } +} + +function drawGlassIcon(context, radius) { + context.save(); + context.clearRect(0,0,context.canvas.width, + context.canvas.height); + + context.shadowColor = 'rgba(52, 72, 35, 0.5)'; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.shadowBlur = 2; + + context.beginPath(); + + context.translate(context.canvas.width/2, + context.canvas.height/2); + + context.beginPath(); + context.lineWidth = 1.5; + context.arc(0, 0, radius+3, 0, Math.PI*2, false); + context.strokeStyle = 'rgb(52, 72, 35)'; + context.stroke(); + + context.beginPath(); + context.lineWidth = 0.5; + context.strokeStyle = 'rgba(255,255,255,0.6)'; + context.arc(0, 0, radius+6, 0, Math.PI*2, false); + context.stroke(); + + context.restore(); + }; +/* +function drawGlassIcon(context, radius) { + context.save(); + context.clearRect(0,0,context.canvas.width, + context.canvas.height); + + context.shadowColor = 'rgba(100, 150, 255, 0.5)'; + context.shadowOffsetX = -1; + context.shadowOffsetY = -1; + context.shadowBlur = 2; + + context.beginPath(); + + context.translate(context.canvas.width/2, + context.canvas.height/2); + + context.beginPath(); + context.lineWidth = 2; + context.arc(0, 0, radius+4, 0, Math.PI*2, false); + context.strokeStyle = 'rgba(52, 72, 35, 0.6)'; + context.stroke(); + + context.beginPath(); + context.lineWidth = 1; + context.strokeStyle = 'silver'; + context.arc(0, 0, radius+6, 0, Math.PI*2, false); + context.stroke(); + context.restore(); +} +*/ +function drawMagnificationText(value, percent) { + scaleOutput.innerHTML = value; + percent = percent < 0.35 ? 0.35 : percent; + scaleOutput.style.fontSize = percent*MAXIMUM_SCALE/2 + 'em'; +} + +function updateMagnifyingGlass() { + eraseMagnifyingGlass(); + drawMagnifyingGlass({ x: magnifyingGlassX, y: magnifyingGlassY }); +} + +function step(time, lastTime, mouse, speed) { + var elapsedTime = time - lastTime, + nextLeft = mouse.x - magnifyingGlassRadius + speed.vx*(elapsedTime/10), + nextTop = mouse.y - magnifyingGlassRadius + speed.vy*(elapsedTime/10), + nextRight = nextLeft + magnifyingGlassRadius*2, + nextBottom = nextTop + magnifyingGlassRadius*2; + + eraseMagnifyingGlass(); + + if (nextLeft < 0) { + speed.vx = -speed.vx; + mouse.x = magnifyingGlassRadius; + } + else if (nextRight > canvas.width) { + speed.vx = -speed.vx; + mouse.x = canvas.width - magnifyingGlassRadius; + } + + if (nextTop < 0) { + speed.vy = -speed.vy; + mouse.y = magnifyingGlassRadius; + } + else if (nextBottom > canvas.height) { + speed.vy = -speed.vy; + mouse.y = canvas.height - magnifyingGlassRadius; + } + + mouse.x += speed.vx*(elapsedTime/10); + mouse.y += speed.vy*(elapsedTime/10); + + drawMagnifyingGlass(mouse); +} +function animate(mouse, speed) { + var time, lastTime = 0, elapsedTime; + animating = true; + + if (lastTime === 0) { + lastTime = +new Date; + } + + animationLoop = setInterval(function() { + var time = + new Date; + step(time, lastTime, mouse, speed); + lastTime = time; + }, 1000/60); +} + +function didThrow() { + var elapsedTime = mouseup.time - mousedown.time; + var elapsedMotion = Math.abs(mouseup.x - mousedown.x) + + Math.abs(mouseup.y - mousedown.y); + return false; //(elapsedMotion / elapsedTime * 10) > 3; +} + +// Touch Event Handlers........................................ + +function isPinching (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + return changed === 1 || changed === 2 && touching === 2; +} + +function isDragging (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + return changed === 1 && touching === 1; +} + +canvas.ontouchstart = function (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + e.preventDefault(e); + + if (isDragging(e)) { + mouseDownOrTouchStart(windowToCanvas(e.pageX, e.pageY)); + } + else if (isPinching(e)) { + var touch1 = e.touches.item(0), + touch2 = e.touches.item(1), + point1 = windowToCanvas(touch1.pageX, touch1.pageY), + point2 = windowToCanvas(touch2.pageX, touch2.pageY); + + distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.x - point1.x, 2)); + pinchRatio = magnificationScale / distance; + } +}; + +canvas.ontouchmove = function (e) { + var changed = e.changedTouches.length, + touching = e.touches.length, + distance, touch1, touch2; + + e.preventDefault(e); + + if (isDragging(e)) { + mouseMoveOrTouchMove(windowToCanvas(e.pageX, e.pageY)); + } + else if (isPinching(e)) { + var touch1 = e.touches.item(0), + touch2 = e.touches.item(1), + point1 = windowToCanvas(touch1.pageX, touch1.pageY), + point2 = windowToCanvas(touch2.pageX, touch2.pageY), + scale; + + distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.x - point1.x, 2)); + scale = pinchRatio * distance; + + if (scale > 1 && scale < 3) { + magnificationScale = parseFloat(pinchRatio * distance).toFixed(2); + draw(); + } + } +}; + +canvas.ontouchend = function (e) { + e.preventDefault(e); + mouseUpOrTouchEnd(windowToCanvas(e.pageX, e.pageY)); +}; + +// Mouse Event Handlers........................................ + +canvas.onmousedown = function (e) { + e.preventDefault(e); + mouseDownOrTouchStart(windowToCanvas(e.clientX, e.clientY)); +}; + +canvas.onmousemove = function (e) { + e.preventDefault(e); + mouseMoveOrTouchMove(windowToCanvas(e.clientX, e.clientY)); +}; + +canvas.onmouseup = function (e) { + e.preventDefault(e); + mouseUpOrTouchEnd(windowToCanvas(e.clientX, e.clientY)); +}; + +function mouseDownOrTouchStart(mouse) { + mousedown = { x: mouse.x, y: mouse.y, time: (new Date).getTime() }; + + if (animating) { + animating = false; + clearInterval(animationLoop); + eraseMagnifyingGlass(); + } + else { + dragging = true; + context.save(); + } +}; + +function mouseMoveOrTouchMove(mouse) { + if (dragging) { + eraseMagnifyingGlass(); + drawMagnifyingGlass(mouse); + } +}; + +function mouseUpOrTouchEnd(mouse) { + mouseup = { x: mouse.x, y: mouse.y, time: (new Date).getTime() }; + + if (dragging) { + if (didThrow()) { + velocityX = (mouseup.x-mousedown.x)/100; + velocityY = (mouseup.y-mousedown.y)/100; + animate(mouse, { vx: velocityX, vy: velocityY }); + } + else { + //eraseMagnifyingGlass(); + } + } + dragging = false; +}; + +// Slider Event Handlers....................................... + +magnifyZoomSlider.addChangeListener( function(e) { + var maxRadius = (glassSizeCanvas.width/2-7); + percent = magnifyZoomSlider.knobPercent, + value = parseFloat(1 + percent * 2).toFixed(2); + + drawMagnificationText(value, percent); + magnificationScale = value; + updateMagnifyingGlass(); +}); + +glassSlider.addChangeListener( function(e) { + var maxRadius = glassSizeCanvas.width/2-5, + percent = parseFloat(glassSlider.knobPercent), + value = 25 + new Number((percent * 175).toFixed(0)); + + magnifyingGlassRadius = value + drawGlassIcon(glassSizeContext, maxRadius * percent); + updateMagnifyingGlass(); +}); + +// Initialization.............................................. + +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'rgba(250, 250, 0, 0.5)'; +context.shadowColor = 'rgba(0, 0, 0, 0.5)'; +context.shadowOffsetX = 10; +context.shadowOffsetY = 10; +context.shadowBlur = 20; + +function draw() { + var maxRadius = (glassSizeCanvas.width/2-7), + percent = parseFloat(glassSlider.knobPercent); + + context.drawImage(image, 0, 0, canvas.width, canvas.height); + drawGlassIcon(glassSizeContext, maxRadius * 0.5); + drawMagnificationText(magnificationScale, percent); + drawMagnifyingGlass({ x: magnifyingGlassX, y: magnifyingGlassY }); +} + +image.src = '../../shared/images/camp.png'; +image.onload = function(e) { + draw(); +}; + +drawGlassIcon(glassSizeContext, (glassSizeCanvas.width/2-7)/2 ); + +canvas.addEventListener('dragenter', function (e) { + e.preventDefault(); + e.dataTransfer.effectAllowed = 'copy'; +}, false); + +canvas.addEventListener('dragover', function (e) { + e.preventDefault(); +}, false); + +window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + +canvas.addEventListener('drop', function (e) { + var file = e.dataTransfer.files[0]; + + window.requestFileSystem(window.TEMPORARY, 5*1024*1024, + function (fs) { + fs.root.getFile(file.name, {create: true}, + function (fileEntry) { + fileEntry.createWriter( function (writer) { + writer.write(file); + }); + image.src = fileEntry.toURL(); + }, + + function (e) { + } + ); + }, + + function (e) { + alert(e.code); + } + ); +}, false); + +magnifyZoomSlider.appendTo('magnificationSliderDiv'); +glassSlider.appendTo('glassSizeSliderDiv'); + +magnifyZoomSlider.draw(); +glassSlider.draw(); diff --git a/canvas/ch04/example-4.22/example.html b/canvas/ch04/example-4.22/example.html new file mode 100644 index 0000000..014668c --- /dev/null +++ b/canvas/ch04/example-4.22/example.html @@ -0,0 +1,190 @@ + + + + + + Magnifying Glass + + + + +
+
+ 1.5 +
+ + + Canvas not supported + +
+ +
+ + + Canvas not supported + +
+ + + + + + + diff --git a/canvas/ch04/example-4.22/example.js b/canvas/ch04/example-4.22/example.js new file mode 100644 index 0000000..004d9b2 --- /dev/null +++ b/canvas/ch04/example-4.22/example.js @@ -0,0 +1,610 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + image = new Image(), + imageData = null, + dragging = false, + + glassSizeCanvas = document.getElementById('glassSizeCanvas'), + glassSizeContext = glassSizeCanvas.getContext('2d'), + + MAXIMUM_SCALE = 4.0, + scaleOutput = document.getElementById('scaleOutput'), + + magnifyingGlassRadius = 120, + magnificationScale = scaleOutput.innerHTML, + magnifyRectangle = {}, + + MAX_GLASS_RADIUS = 350, + + magnifyingGlassX = 512, + magnifyingGlassY = 340, + + magnifyZoomSlider = new COREHTML5.Slider('navy', + 'rgb(80, 140, 230)', + 0.25, // knob percent + 90, // take up % of width + 55), // take up % of height + + glassSlider = new COREHTML5.Slider('navy', 'rgb(80, 140, 230)', 0.50, 90, 55), + + animating = false, + animationLoop = null, + + mousedown = null, + mouseup = null, + + canvasRatio = canvas.height / canvas.width, + pinchRatio; + +// Functions................................................... + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function calculateMagnifyRectangle(mouse) { + var top, + left, + bottom, + right; + + magnifyRectangle.x = mouse.x - magnifyingGlassRadius; + magnifyRectangle.y = mouse.y - magnifyingGlassRadius; + magnifyRectangle.width = magnifyingGlassRadius*2 + 2*context.lineWidth; + magnifyRectangle.height = magnifyingGlassRadius*2 + 2*context.lineWidth; + + top = magnifyRectangle.y; + left = magnifyRectangle.x; + bottom = magnifyRectangle.y + magnifyRectangle.height; + right = magnifyRectangle.x + magnifyRectangle.width; + + if (left < 0) { + magnifyRectangle.width += left; + magnifyRectangle.x = 0; + } + else if (right > canvas.width) { + magnifyRectangle.width -= right - canvas.width; + } + + if (top < 0) { + magnifyRectangle.height += magnifyRectangle.y; + magnifyRectangle.y = 0; + } + else if (bottom > canvas.height) { + magnifyRectangle.height -= bottom - canvas.height; + } +} +/* +function drawMagnifyingGlassCircle(mouse) { + context.save(); + context.lineWidth = 3; + context.strokeStyle = 'rgba(0, 0, 255, 0.3)'; + + context.shadowColor = 'rgba(0, 0, 155, 1)'; + context.shadowOffsetX = '-10'; + context.shadowOffsetY = '-10'; + context.shadowBlur = '20'; + + context.beginPath(); + context.arc(mouse.x, mouse.y, + magnifyingGlassRadius, 0, Math.PI*2, false); + context.clip(); + context.shadowColor = 'cornflowerblue'; + context.strokeStyle = 'skyblue'; + context.stroke(); + + context.beginPath(); + context.lineWidth = 1; + context.strokeStyle = 'rgba(100, 149, 240, 0.7)'; + context.arc(mouse.x, mouse.y, + magnifyingGlassRadius-1, 0, Math.PI*2, false); + context.stroke(); + + context.beginPath(); + context.strokeStyle = 'rgba(100, 149, 240, 0.5)'; + context.lineWidth = 2; + context.arc(mouse.x, mouse.y, + magnifyingGlassRadius-1, 0, Math.PI*2, false); + context.stroke(); + + context.beginPath(); + context.strokeStyle = 'rgba(255, 255, 0, 0.3)'; + context.lineWidth = 1; + context.arc(mouse.x, mouse.y, + magnifyingGlassRadius-3, 0, Math.PI*2, false); + context.stroke(); + + context.strokeStyle = 'rgba(0, 0, 0, 0.5)'; + context.shadowColor = 'rgba(0, 0, 0, 0.8)'; + context.shadowOffsetX = '10'; + context.shadowOffsetY = '10'; + context.shadowBlur = '20'; + context.lineWidth = 4; + context.stroke(); + + context.lineWidth = 2; + context.strokeStyle = 'silver'; + context.shadowColor = 'goldenrod'; + context.stroke(); + + context.restore(); +} +*/ +function setClip() { + context.beginPath(); +/* +context.strokeStyle = 'blue'; +context.lineWidth = 0.5; +context.strokeRect(magnifyingGlassX-magnifyingGlassRadius, magnifyingGlassY-magnifyingGlassRadius, magnifyingGlassRadius*2+3, magnifyingGlassRadius*2+3); + */ + //context.beginPath(); + context.arc(magnifyingGlassX, magnifyingGlassY, + magnifyingGlassRadius, 0, Math.PI*2, false); + + context.clip(); +} + +function drawMagnifyingGlassCircle(mouse) { + var gradientThickness = this.magnifyingGlassRadius / 7; + + gradientThickness = gradientThickness < 10 ? 10 : gradientThickness; + gradientThickness = gradientThickness > 40 ? 40 : gradientThickness; + + gradientThickness = 10; + this.context.save(); + this.context.lineWidth = gradientThickness; + this.context.strokeStyle = 'rgb(0, 0, 255, 0.3)'; + + this.context.beginPath(); + this.context.arc(mouse.x, mouse.y, + this.magnifyingGlassRadius, 0, Math.PI*2, false); + this.context.clip(); + + var gradient = this.context.createRadialGradient( + mouse.x, mouse.y, this.magnifyingGlassRadius-gradientThickness, + mouse.x, mouse.y, this.magnifyingGlassRadius); + gradient.addColorStop(0, 'rgba(0,0,0,0.2)'); + gradient.addColorStop(0.80, 'rgb(235,237,255)'); + gradient.addColorStop(0.90, 'rgb(235,237,255)'); + gradient.addColorStop(1.0, 'rgba(150,150,150,0.9)'); + + this.context.shadowColor = 'rgba(52, 72, 35, 1.0)'; + this.context.shadowOffsetX = 2; + this.context.shadowOffsetY = 2; + this.context.shadowBlur = 20; + + this.context.strokeStyle = gradient; + this.context.stroke(); + + this.context.beginPath(); + this.context.arc(mouse.x, mouse.y, + this.magnifyingGlassRadius-gradientThickness/2, 0, Math.PI*2, false); + this.context.clip(); + + this.context.lineWidth = gradientThickness; + this.context.strokeStyle = 'rgba(0,0,0,0.06)'; + this.context.stroke(); + + this.context.restore(); +}; + +function drawMagnifyingGlass(mouse) { + var scaledMagnifyRectangle; + + if (window.netscape && netscape.security.PrivilegeManager) + netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); + + magnifyingGlassX = mouse.x; + magnifyingGlassY = mouse.y; + + calculateMagnifyRectangle(mouse); + + imageData = context.getImageData(magnifyRectangle.x, + magnifyRectangle.y, + magnifyRectangle.width, + magnifyRectangle.height); + context.save(); + + scaledMagnifyRectangle = { + width: magnifyRectangle.width * magnificationScale, + height: magnifyRectangle.height * magnificationScale + }; + + setClip(); + + context.drawImage(canvas, + magnifyRectangle.x, magnifyRectangle.y, + magnifyRectangle.width, magnifyRectangle.height, + + magnifyRectangle.x + magnifyRectangle.width/2 - + scaledMagnifyRectangle.width/2, + + magnifyRectangle.y + magnifyRectangle.height/2 - + scaledMagnifyRectangle.height/2, + + scaledMagnifyRectangle.width, + scaledMagnifyRectangle.height); + + context.restore(); + + drawMagnifyingGlassCircle(mouse); +} + +function eraseMagnifyingGlass() { // Called when the mouse moves + if (imageData != null) { + context.putImageData(imageData, + magnifyRectangle.x, + magnifyRectangle.y); + } +} + +function drawGlassIcon(context, radius) { + context.save(); + context.clearRect(0,0,context.canvas.width, + context.canvas.height); + + context.shadowColor = 'rgba(52, 72, 35, 0.5)'; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.shadowBlur = 2; + + context.beginPath(); + + context.translate(context.canvas.width/2, + context.canvas.height/2); + + context.beginPath(); + context.lineWidth = 1.5; + context.arc(0, 0, radius+3, 0, Math.PI*2, false); + context.strokeStyle = 'rgb(52, 72, 35)'; + context.stroke(); + + context.beginPath(); + context.lineWidth = 0.5; + context.strokeStyle = 'rgba(255,255,255,0.6)'; + context.arc(0, 0, radius+6, 0, Math.PI*2, false); + context.stroke(); + + context.restore(); + }; +/* +function drawGlassIcon(context, radius) { + context.save(); + context.clearRect(0,0,context.canvas.width, + context.canvas.height); + + context.shadowColor = 'rgba(100, 150, 255, 0.5)'; + context.shadowOffsetX = -1; + context.shadowOffsetY = -1; + context.shadowBlur = 2; + + context.beginPath(); + + context.translate(context.canvas.width/2, + context.canvas.height/2); + + context.beginPath(); + context.lineWidth = 2; + context.arc(0, 0, radius+4, 0, Math.PI*2, false); + context.strokeStyle = 'rgba(52, 72, 35, 0.6)'; + context.stroke(); + + context.beginPath(); + context.lineWidth = 1; + context.strokeStyle = 'silver'; + context.arc(0, 0, radius+6, 0, Math.PI*2, false); + context.stroke(); + context.restore(); +} +*/ +function drawMagnificationText(value, percent) { + scaleOutput.innerHTML = value; + percent = percent < 0.35 ? 0.35 : percent; + scaleOutput.style.fontSize = percent*MAXIMUM_SCALE/2 + 'em'; +} + +function updateMagnifyingGlass() { + eraseMagnifyingGlass(); + drawMagnifyingGlass({ x: magnifyingGlassX, y: magnifyingGlassY }); +} + +function step(time, lastTime, mouse, speed) { + var elapsedTime = time - lastTime, + nextLeft = mouse.x - magnifyingGlassRadius + speed.vx*(elapsedTime/10), + nextTop = mouse.y - magnifyingGlassRadius + speed.vy*(elapsedTime/10), + nextRight = nextLeft + magnifyingGlassRadius*2, + nextBottom = nextTop + magnifyingGlassRadius*2; + + eraseMagnifyingGlass(); + + if (nextLeft < 0) { + speed.vx = -speed.vx; + mouse.x = magnifyingGlassRadius; + } + else if (nextRight > canvas.width) { + speed.vx = -speed.vx; + mouse.x = canvas.width - magnifyingGlassRadius; + } + + if (nextTop < 0) { + speed.vy = -speed.vy; + mouse.y = magnifyingGlassRadius; + } + else if (nextBottom > canvas.height) { + speed.vy = -speed.vy; + mouse.y = canvas.height - magnifyingGlassRadius; + } + + mouse.x += speed.vx*(elapsedTime/10); + mouse.y += speed.vy*(elapsedTime/10); + + drawMagnifyingGlass(mouse); +} +function animate(mouse, speed) { + var time, lastTime = 0, elapsedTime; + animating = true; + + if (lastTime === 0) { + lastTime = +new Date; + } + + animationLoop = setInterval(function() { + var time = + new Date; + step(time, lastTime, mouse, speed); + lastTime = time; + }, 1000/60); +} + +function didThrow() { + var elapsedTime = mouseup.time - mousedown.time; + var elapsedMotion = Math.abs(mouseup.x - mousedown.x) + + Math.abs(mouseup.y - mousedown.y); + return false; //(elapsedMotion / elapsedTime * 10) > 3; +} + +// Touch Event Handlers........................................ + +function isPinching (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + return changed === 1 || changed === 2 && touching === 2; +} + +function isDragging (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + return changed === 1 && touching === 1; +} + +canvas.ontouchstart = function (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + e.preventDefault(e); + + if (isDragging(e)) { + mouseDownOrTouchStart(windowToCanvas(e.pageX, e.pageY)); + } + else if (isPinching(e)) { + var touch1 = e.touches.item(0), + touch2 = e.touches.item(1), + point1 = windowToCanvas(touch1.pageX, touch1.pageY), + point2 = windowToCanvas(touch2.pageX, touch2.pageY); + + distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.x - point1.x, 2)); + pinchRatio = magnificationScale / distance; + } +}; + +canvas.ontouchmove = function (e) { + var changed = e.changedTouches.length, + touching = e.touches.length, + distance, touch1, touch2; + + e.preventDefault(e); + + if (isDragging(e)) { + mouseMoveOrTouchMove(windowToCanvas(e.pageX, e.pageY)); + } + else if (isPinching(e)) { + var touch1 = e.touches.item(0), + touch2 = e.touches.item(1), + point1 = windowToCanvas(touch1.pageX, touch1.pageY), + point2 = windowToCanvas(touch2.pageX, touch2.pageY), + scale; + + distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.x - point1.x, 2)); + scale = pinchRatio * distance; + + if (scale > 1 && scale < 3) { + magnificationScale = parseFloat(pinchRatio * distance).toFixed(2); + draw(); + } + } +}; + +canvas.ontouchend = function (e) { + e.preventDefault(e); + mouseUpOrTouchEnd(windowToCanvas(e.pageX, e.pageY)); +}; + +// Mouse Event Handlers........................................ + +canvas.onmousedown = function (e) { + e.preventDefault(e); + mouseDownOrTouchStart(windowToCanvas(e.clientX, e.clientY)); +}; + +canvas.onmousemove = function (e) { + e.preventDefault(e); + mouseMoveOrTouchMove(windowToCanvas(e.clientX, e.clientY)); +}; + +canvas.onmouseup = function (e) { + e.preventDefault(e); + mouseUpOrTouchEnd(windowToCanvas(e.clientX, e.clientY)); +}; + +function mouseDownOrTouchStart(mouse) { + mousedown = { x: mouse.x, y: mouse.y, time: (new Date).getTime() }; + + if (animating) { + animating = false; + clearInterval(animationLoop); + eraseMagnifyingGlass(); + } + else { + dragging = true; + context.save(); + } +}; + +function mouseMoveOrTouchMove(mouse) { + if (dragging) { + eraseMagnifyingGlass(); + drawMagnifyingGlass(mouse); + } +}; + +function mouseUpOrTouchEnd(mouse) { + mouseup = { x: mouse.x, y: mouse.y, time: (new Date).getTime() }; + + if (dragging) { + if (didThrow()) { + velocityX = (mouseup.x-mousedown.x)/100; + velocityY = (mouseup.y-mousedown.y)/100; + animate(mouse, { vx: velocityX, vy: velocityY }); + } + else { + //eraseMagnifyingGlass(); + } + } + dragging = false; +}; + +// Slider Event Handlers....................................... + +magnifyZoomSlider.addChangeListener( function(e) { + var maxRadius = (glassSizeCanvas.width/2-7); + percent = magnifyZoomSlider.knobPercent, + value = parseFloat(1 + percent * 2).toFixed(2); + + drawMagnificationText(value, percent); + magnificationScale = value; + updateMagnifyingGlass(); +}); + +glassSlider.addChangeListener( function(e) { + var maxRadius = glassSizeCanvas.width/2-5, + percent = parseFloat(glassSlider.knobPercent), + value = 25 + new Number((percent * 175).toFixed(0)); + + magnifyingGlassRadius = value + drawGlassIcon(glassSizeContext, maxRadius * percent); + updateMagnifyingGlass(); +}); + +// Initialization.............................................. + +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'rgba(250, 250, 0, 0.5)'; +context.shadowColor = 'rgba(0, 0, 0, 0.5)'; +context.shadowOffsetX = 10; +context.shadowOffsetY = 10; +context.shadowBlur = 20; + +function draw() { + var maxRadius = (glassSizeCanvas.width/2-7), + percent = parseFloat(glassSlider.knobPercent); + + context.drawImage(image, 0, 0, canvas.width, canvas.height); + drawGlassIcon(glassSizeContext, maxRadius * 0.5); + drawMagnificationText(magnificationScale, percent); + drawMagnifyingGlass({ x: magnifyingGlassX, y: magnifyingGlassY }); +} + +image.src = '../../shared/images/camp.png'; +image.onload = function(e) { + draw(); +}; + +drawGlassIcon(glassSizeContext, (glassSizeCanvas.width/2-7)/2 ); + +canvas.addEventListener('dragenter', function (e) { + e.preventDefault(); + e.dataTransfer.effectAllowed = 'copy'; +}, false); + +canvas.addEventListener('dragover', function (e) { + e.preventDefault(); +}, false); + +window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + +canvas.addEventListener('drop', function (e) { + var file = e.dataTransfer.files[0]; + + window.requestFileSystem(window.TEMPORARY, 5*1024*1024, + function (fs) { + fs.root.getFile(file.name, {create: true}, + function (fileEntry) { + fileEntry.createWriter( function (writer) { + writer.write(file); + }); + image.src = fileEntry.toURL(); + }, + + function (e) { + alert(e.code); + } + ); + }, + + function (e) { + alert(e.code); + } + ); +}, false); + +magnifyZoomSlider.appendTo('magnificationSliderDiv'); +glassSlider.appendTo('glassSizeSliderDiv'); + +magnifyZoomSlider.draw(); +glassSlider.draw(); diff --git a/canvas/ch04/example-4.23/README.txt b/canvas/ch04/example-4.23/README.txt new file mode 100644 index 0000000..d428503 --- /dev/null +++ b/canvas/ch04/example-4.23/README.txt @@ -0,0 +1 @@ +Due to copyright restrictions, you need to put your own video file in this directory, and reference it in example.html for this example to work. diff --git a/canvas/ch04/example-4.23/example.html b/canvas/ch04/example-4.23/example.html new file mode 100644 index 0000000..24a5b89 --- /dev/null +++ b/canvas/ch04/example-4.23/example.html @@ -0,0 +1,65 @@ + + + + + Video + + + + + + + + + Canvas not supported + + + + + + diff --git a/canvas/ch04/example-4.23/example.js b/canvas/ch04/example-4.23/example.js new file mode 100644 index 0000000..1b1d35d --- /dev/null +++ b/canvas/ch04/example-4.23/example.js @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + video = document.getElementById('video'); + +function animate() { + if (!video.ended) { + context.drawImage(video, 0, 0, canvas.width, canvas.height); + window.requestNextAnimationFrame(animate); + } +} + +video.onload = function (e) { + video.play(); + window.requestNextAnimationFrame(animate); +}; + +alert('This example plays a video, but due to copyright restrictions and size limitations, the video is not included in the code for this example. To make this example work, download a video, and replace the two source elements in example.html to refer to your video.'); diff --git a/canvas/ch04/example-4.25/README.txt b/canvas/ch04/example-4.25/README.txt new file mode 100644 index 0000000..d428503 --- /dev/null +++ b/canvas/ch04/example-4.25/README.txt @@ -0,0 +1 @@ +Due to copyright restrictions, you need to put your own video file in this directory, and reference it in example.html for this example to work. diff --git a/canvas/ch04/example-4.25/example.html b/canvas/ch04/example-4.25/example.html new file mode 100644 index 0000000..5adb956 --- /dev/null +++ b/canvas/ch04/example-4.25/example.html @@ -0,0 +1,75 @@ + + + + + Video + + + + + + + + + Canvas not supported + + +
+ + Color + Flip +
+ + + + + diff --git a/canvas/ch04/example-4.25/example.js b/canvas/ch04/example-4.25/example.js new file mode 100644 index 0000000..d107a4a --- /dev/null +++ b/canvas/ch04/example-4.25/example.js @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + offscreenCanvas = document.createElement('canvas'), + offscreenContext = offscreenCanvas.getContext('2d'), + context = canvas.getContext('2d'), + video = document.getElementById('video'), + controlButton = document.getElementById('controlButton'), + flipCheckbox = document.getElementById('flipCheckbox'), + colorCheckbox = document.getElementById('colorCheckbox'), + imageData, + poster = new Image(); + +// Functions..................................................... + +function removeColor() { + var data, + width, + average; + + imageData = offscreenContext.getImageData(0, 0, + offscreenCanvas.width, offscreenCanvas.height); + + data = imageData.data; + width = data.width; + + for (i=0; i < data.length-4; i += 4) { + average = (data[i] + data[i+1] + data[i+2]) / 3; + data[i] = average; + data[i+1] = average; + data[i+2] = average; + } + + offscreenContext.putImageData(imageData, 0, 0); +} + +function drawFlipped() { + context.save(); + + context.translate(canvas.width/2, canvas.height/2); + context.rotate(Math.PI); + context.translate(-canvas.width/2, -canvas.height/2); + context.drawImage(offscreenCanvas, 0, 0); + + context.restore(); +} + +function nextVideoFrame() { + if (video.ended) { + controlButton.value = 'Play'; + } + else { + offscreenContext.drawImage(video, 0, 0); + + if (!colorCheckbox.checked) + removeColor(); + + if (flipCheckbox.checked) + drawFlipped(); + else + context.drawImage(offscreenCanvas, 0, 0); + + requestNextAnimationFrame(nextVideoFrame); + } +} + +function startPlaying() { + requestNextAnimationFrame(nextVideoFrame); + video.play(); +} + +function stopPlaying() { + video.pause(); +} + +// Event handlers............................................... + +controlButton.onclick = function(e) { + if (controlButton.value === 'Play') { + startPlaying(); + controlButton.value = 'Pause'; + } + else { + stopPlaying(); + controlButton.value = 'Play'; + } +} + +poster.onload = function() { + context.drawImage(poster, 0, 0); +}; + +// Initialization................................................ + +poster.src = '../../shared/images/smurfposter.png'; + +offscreenCanvas.width = canvas.width; +offscreenCanvas.height = canvas.height; + +alert('This example plays a video, but due to copyright restrictions and size limitations, the video is not included in the code for this example. To make this example work, download a video, and replace the two source elements in example.html to refer to your video.'); diff --git a/canvas/ch04/example-4.4/example.html b/canvas/ch04/example-4.4/example.html new file mode 100644 index 0000000..5739167 --- /dev/null +++ b/canvas/ch04/example-4.4/example.html @@ -0,0 +1,86 @@ + + + + + + Scaling images + + + + + +
+ 1.0 + +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.4/example.js b/canvas/ch04/example-4.4/example.js new file mode 100644 index 0000000..308bbd1 --- /dev/null +++ b/canvas/ch04/example-4.4/example.js @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + image = new Image(), + + scaleSlider = document.getElementById('scaleSlider'), + scaleOutput = document.getElementById('scaleOutput'), + scale = 1.0, + MINIMUM_SCALE = 1.0, + MAXIMUM_SCALE = 3.0; + +// Functions.................................................... + +function drawImage() { + var w = canvas.width, + h = canvas.height, + sw = w * scale, + sh = h * scale; + + context.clearRect(0,0,canvas.width,canvas.height); + context.drawImage(image, -sw/2 + w/2, -sh/2 + h/2, sw, sh); +} + +function drawScaleText(value) { + var text = parseFloat(value).toFixed(2); + var percent = parseFloat(value - MINIMUM_SCALE) / + parseFloat(MAXIMUM_SCALE - MINIMUM_SCALE); + + scaleOutput.innerText = text; + percent = percent < 0.35 ? 0.35 : percent; + scaleOutput.style.fontSize = percent*MAXIMUM_SCALE/1.5 + 'em'; +} + +// Event Handlers............................................... + +scaleSlider.onchange = function(e) { + scale = e.target.value; + + if (scale < MINIMUM_SCALE) scale = MINIMUM_SCALE; + else if (scale > MAXIMUM_SCALE) scale = MAXIMUM_SCALE; + + drawScaleText(scale); + drawImage(); +} + +// Initialization............................................... + +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'yellow'; +context.shadowColor = 'rgba(50, 50, 50, 1.0)'; +context.shadowOffsetX = 5; +context.shadowOffsetY = 5; +context.shadowBlur = 10; + +image.src = '../../shared/images/waterfall.png'; +image.onload = function(e) { + drawImage(); + drawScaleText(scaleSlider.value); +}; diff --git a/canvas/ch04/example-4.6/example.html b/canvas/ch04/example-4.6/example.html new file mode 100644 index 0000000..db9afd2 --- /dev/null +++ b/canvas/ch04/example-4.6/example.html @@ -0,0 +1,87 @@ + + + + + + Watermarks: Drawing a Canvas into a Canvas + + + + + +
+ 1.0 + +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.6/example.js b/canvas/ch04/example-4.6/example.js new file mode 100644 index 0000000..c35f687 --- /dev/null +++ b/canvas/ch04/example-4.6/example.js @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + image = new Image(), + + scaleOutput = document.getElementById('scaleOutput'); + scaleSlider = document.getElementById('scaleSlider'), + scale = scaleSlider.value, + scale = 1.0, + MINIMUM_SCALE = 1.0, + MAXIMUM_SCALE = 3.0; + +// Functions..................................................... + +function drawScaled() { + var w = canvas.width, + h = canvas.height, + sw = w * scale, + sh = h * scale; + + // Clear the canvas, and draw the image scaled to canvas size + + context.clearRect(0, 0, canvas.width, canvas.height); + context.drawImage(image, 0, 0, canvas.width, canvas.height); + + // Draw the watermark on top of the image + + drawWatermark(); + + // Finally, draw the canvas scaled according to the current + // scale, back into itself. Note that the source and + // destination canvases are the same canvas. + + context.drawImage(canvas, 0, 0, canvas.width, canvas.height, + -sw/2 + w/2, -sh/2 + h/2, sw, sh); +} + +function drawScaleText(value) { + var text = parseFloat(value).toFixed(2); + var percent = parseFloat(value - MINIMUM_SCALE) / + parseFloat(MAXIMUM_SCALE - MINIMUM_SCALE); + + scaleOutput.innerText = text; + percent = percent < 0.35 ? 0.35 : percent; + scaleOutput.style.fontSize = percent*MAXIMUM_SCALE/1.5 + 'em'; +} + +function drawWatermark() { + var lineOne = 'Copyright', + lineTwo = 'Acme Inc.', + textMetrics = null, + FONT_HEIGHT = 128; + + context.save(); + context.font = FONT_HEIGHT + 'px Arial'; + + textMetrics = context.measureText(lineOne); + + context.globalAlpha = 0.6; + context.translate(canvas.width/2, + canvas.height/2-FONT_HEIGHT/2); + + context.fillText(lineOne, -textMetrics.width/2, 0); + context.strokeText(lineOne, -textMetrics.width/2, 0); + + textMetrics = context.measureText(lineTwo); + context.fillText(lineTwo, -textMetrics.width/2, FONT_HEIGHT); + context.strokeText(lineTwo, -textMetrics.width/2, FONT_HEIGHT); + + context.restore(); +} + +// Event Handlers................................................ + +scaleSlider.onchange = function(e) { + scale = e.target.value; + + if (scale < MINIMUM_SCALE) scale = MINIMUM_SCALE; + else if (scale > MAXIMUM_SCALE) scale = MAXIMUM_SCALE; + + drawScaled(); + drawScaleText(scale); +} + +// Initialization................................................ + +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'yellow'; +context.shadowColor = 'rgba(50, 50, 50, 1.0)'; +context.shadowOffsetX = 5; +context.shadowOffsetY = 5; +context.shadowBlur = 10; + +var glassSize = 150; +var scale = 1.0; + +image.src = '../../shared/images/lonelybeach.png'; +image.onload = function(e) { + var maxRadius = 200; + var percent = 20; + context.drawImage(image, 0, 0, canvas.width, canvas.height); + drawWatermark(); + drawScaleText(scaleSlider.value); +}; diff --git a/canvas/ch04/example-4.8/example.html b/canvas/ch04/example-4.8/example.html new file mode 100644 index 0000000..db9afd2 --- /dev/null +++ b/canvas/ch04/example-4.8/example.html @@ -0,0 +1,87 @@ + + + + + + Watermarks: Drawing a Canvas into a Canvas + + + + + +
+ 1.0 + +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.8/example.js b/canvas/ch04/example-4.8/example.js new file mode 100644 index 0000000..d5544a1 --- /dev/null +++ b/canvas/ch04/example-4.8/example.js @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + offscreenCanvas = document.createElement('canvas'); + offscreenContext = offscreenCanvas.getContext('2d'), + image = new Image(), + + scaleOutput = document.getElementById('scaleOutput'); + scaleSlider = document.getElementById('scaleSlider'); + canvasRadio = document.getElementById('canvasRadio'), + imageRadio = document.getElementById('imageRadio'), + scale = scaleSlider.value, + scale = 1.0, + MINIMUM_SCALE = 1.0, + MAXIMUM_SCALE = 3.0; + +// Functions..................................................... + +function drawScaled() { + var w = canvas.width, + h = canvas.height, + sw = w * scale, + sh = h * scale; + + context.drawImage(offscreenCanvas, 0, 0, + offscreenCanvas.width, offscreenCanvas.height, + -sw/2 + w/2, -sh/2 + h/2, sw, sh); +} + +function drawScaleText(value) { + var text = parseFloat(value).toFixed(2); + var percent = parseFloat(value - MINIMUM_SCALE) / + parseFloat(MAXIMUM_SCALE - MINIMUM_SCALE); + + scaleOutput.innerText = text; + percent = percent < 0.35 ? 0.35 : percent; + scaleOutput.style.fontSize = percent*MAXIMUM_SCALE/1.5 + 'em'; +} + +function drawWatermark(context) { + var lineOne = 'Copyright', + lineTwo = 'Acme, Inc.', + textMetrics = null, + FONT_HEIGHT = 128; + + context.save(); + context.fillStyle = 'rgba(100,140,230,0.5);'; + context.strokeStyle = 'yellow'; + context.shadowColor = 'rgba(50, 50, 50, 1.0)'; + context.shadowOffsetX = 5; + context.shadowOffsetY = 5; + context.shadowBlur = 10; + + context.font = FONT_HEIGHT + 'px Arial'; + textMetrics = context.measureText(lineOne); + context.translate(canvas.width/2, canvas.height/2); + context.fillText(lineOne, -textMetrics.width/2, 0); + context.strokeText(lineOne, -textMetrics.width/2, 0); + + textMetrics = context.measureText(lineTwo); + context.fillText(lineTwo, -textMetrics.width/2, FONT_HEIGHT); + context.strokeText(lineTwo, -textMetrics.width/2, FONT_HEIGHT); + context.restore(); +} + +// Event Handlers................................................ + +scaleSlider.onchange = function(e) { + scale = e.target.value; + + if (scale < MINIMUM_SCALE) scale = MINIMUM_SCALE; + else if (scale > MAXIMUM_SCALE) scale = MAXIMUM_SCALE; + + drawScaled(); + drawScaleText(scale); +} + +// Initialization................................................ + +offscreenCanvas.width = canvas.width; +offscreenCanvas.height = canvas.height; + +image.src = '../../shared/images/lonelybeach.png'; +image.onload = function(e) { + context.drawImage(image, 0, 0, canvas.width, canvas.height); + offscreenContext.drawImage(image, 0, 0, + canvas.width, canvas.height); + drawWatermark(context); + drawWatermark(offscreenContext); + drawScaleText(scaleSlider.value); +}; diff --git a/canvas/ch04/example-4.9/example.html b/canvas/ch04/example-4.9/example.html new file mode 100644 index 0000000..a660826 --- /dev/null +++ b/canvas/ch04/example-4.9/example.html @@ -0,0 +1,70 @@ + + + + + + + Rubberbands with getImageData() and putImageData() + + + + + + +
+ +
+ + + Canvas not supported + + + + diff --git a/canvas/ch04/example-4.9/example.js b/canvas/ch04/example-4.9/example.js new file mode 100644 index 0000000..9fea450 --- /dev/null +++ b/canvas/ch04/example-4.9/example.js @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + resetButton = document.getElementById('resetButton'), + + image = new Image(), + imageData, + + mousedown = {}, + rubberbandRectangle = {}, + dragging = false; + +// Functions..................................................... + +function windowToCanvas(canvas, x, y) { + var canvasRectangle = canvas.getBoundingClientRect(); + + return { + x: x - canvasRectangle.left, + y: y - canvasRectangle.top + }; +} + +function captureRubberbandPixels() { + imageData = context.getImageData(rubberbandRectangle.left, + rubberbandRectangle.top, + rubberbandRectangle.width, + rubberbandRectangle.height); +} + +function restoreRubberbandPixels() { + var deviceWidthOverCSSPixels = imageData.width / rubberbandRectangle.width, + deviceHeightOverCSSPixels = imageData.height / rubberbandRectangle.height; + + context.putImageData(imageData, + rubberbandRectangle.left * deviceWidthOverCSSPixels, + rubberbandRectangle.top * deviceHeightOverCSSPixels); +} + +function drawRubberband() { + context.strokeRect(rubberbandRectangle.left + context.lineWidth, + rubberbandRectangle.top + context.lineWidth, + rubberbandRectangle.width - 2*context.lineWidth, + rubberbandRectangle.height - 2*context.lineWidth); +} + +function setRubberbandRectangle(x, y) { + rubberbandRectangle.left = Math.min(x, mousedown.x); + rubberbandRectangle.top = Math.min(y, mousedown.y); + rubberbandRectangle.width = Math.abs(x - mousedown.x), + rubberbandRectangle.height = Math.abs(y - mousedown.y); +} + +function updateRubberband() { + captureRubberbandPixels(); + drawRubberband(); +} + +function rubberbandStart(x, y) { + mousedown.x = x; + mousedown.y = y; + + rubberbandRectangle.left = mousedown.x; + rubberbandRectangle.top = mousedown.y; + + dragging = true; +} + +function rubberbandStretch(x, y) { + if (rubberbandRectangle.width > 2*context.lineWidth && + rubberbandRectangle.height > 2*context.lineWidth) { + if (imageData !== undefined) { + restoreRubberbandPixels(); + } + } + + setRubberbandRectangle(x, y); + + if (rubberbandRectangle.width > 2*context.lineWidth && + rubberbandRectangle.height > 2*context.lineWidth) { + + updateRubberband(); + } +}; + +function rubberbandEnd() { + // Draw and scale image to the on screen canvas. + context.drawImage(canvas, + rubberbandRectangle.left + context.lineWidth*2, + rubberbandRectangle.top + context.lineWidth*2, + rubberbandRectangle.width - 4*context.lineWidth, + rubberbandRectangle.height - 4*context.lineWidth, + 0, 0, canvas.width, canvas.height); + dragging = false; + imageData = undefined; +} + +// Event handlers............................................... + +canvas.onmousedown = function (e) { + var loc = windowToCanvas(canvas, e.clientX, e.clientY); + e.preventDefault(); + rubberbandStart(loc.x, loc.y); +}; + +canvas.onmousemove = function (e) { + var loc; + + if (dragging) { + loc = windowToCanvas(canvas, e.clientX, e.clientY); + rubberbandStretch(loc.x, loc.y); + } +} + +canvas.onmouseup = function (e) { + rubberbandEnd(); +}; + +// Initialization.............................................. + +image.src = '../../shared/images/arch.png'; +image.onload = function () { + context.drawImage(image, 0, 0, canvas.width, canvas.height); +}; + +resetButton.onclick = function(e) { + context.clearRect(0, 0, + canvas.width, canvas.height); + + context.drawImage(image, 0, 0, canvas.width, canvas.height); +}; + +context.strokeStyle = 'yellow'; +context.lineWidth = 2.0; diff --git a/canvas/ch05/example-5.11/example.html b/canvas/ch05/example-5.11/example.html new file mode 100644 index 0000000..0454037 --- /dev/null +++ b/canvas/ch05/example-5.11/example.html @@ -0,0 +1,70 @@ + + + + + Calculating Frames/Second + + + + + +
+ +
+ + + Canvas not supported + + + + + + diff --git a/canvas/ch05/example-5.11/example.js b/canvas/ch05/example-5.11/example.js new file mode 100644 index 0000000..bac8f0e --- /dev/null +++ b/canvas/ch05/example-5.11/example.js @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.querySelector('#canvas'), + context = canvas.getContext('2d'), + paused = true, + discs = [ + { + x: 150, + y: 50, + lastX: 150, + lastY: 50, + velocityX: 3.2, + velocityY: 3.5, + radius: 25, + innerColor: 'rgba(0,255,255,0.3)', + middleColor: 'rgba(0,255,255,0.9)', + outerColor: 'rgba(0,255,255,0.3)', + strokeStyle: 'slateblue', + }, + + { + x: 75, + y: 200, + lastX: 75, + lastY: 200, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(225,225,225,0.1)', + middleColor: 'rgba(225,225,225,0.9)', + outerColor: 'rgba(225,225,225,0.3)', + strokeStyle: 'gray' + }, + + { + x: 100, + y: 300, + lastX: 150, + lastY: 50, + velocityX: 1.2, + velocityY: 1.5, + radius: 25, + innerColor: 'orange', + middleColor: 'yellow', + outerColor: 'gold', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + ], + numDiscs = discs.length, + lastTime = 0, + frameCount = 0, + animateButton = document.querySelector('#animateButton'); + +function eraseBackground() { + context.clearRect(0,0,canvas.width,canvas.height); +} + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'lightgray'; + context.lineWidth = 0.5; + + context.save(); + context.restore(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function update() { + var i = numDiscs, + disc = null; + + while(i--) { + disc = discs[i]; + + if (disc.x + disc.velocityX + disc.radius > context.canvas.width || + disc.x + disc.velocityX - disc.radius < 0) + disc.velocityX = -disc.velocityX; + + if (disc.y + disc.velocityY + disc.radius > context.canvas.height || + disc.y + disc.velocityY - disc.radius < 0) + disc.velocityY= -disc.velocityY; + + disc.x += disc.velocityX; + disc.y += disc.velocityY; + } +} + +function drawDisc(disc) { + var gradient = context.createRadialGradient(disc.x, disc.y, 0, + disc.x, disc.y, disc.radius); + + gradient.addColorStop(0.3, disc.innerColor); + gradient.addColorStop(0.7, disc.middleColor); + gradient.addColorStop(1.0, disc.outerColor); + + context.save(); + context.beginPath(); + context.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false); + context.clip(); + + context.fillStyle = gradient; + context.strokeStyle = disc.strokeStyle; + context.lineWidth = 2; + context.fill(); + context.stroke(); + + context.restore(); +} + +function draw() { + var i = numDiscs, disc; + + i = numDiscs; + while(i--) { + disc = discs[i]; + drawDisc(disc); + disc.lastX = disc.x; + disc.lastY = disc.y; + } + + if (frameCount === 0) { + console.profile('COREHTML5 Animation, basic'); + } + else if (frameCount === 100) { + console.profileEnd(); + frameCount = -1; + } + + if (frameCount !== -1 && frameCount < 100) { + frameCount++; + } +} + +function calculateFps() { + var now = (+new Date), + fps = 1000 / (now - lastTime); + + lastTime = now; + + return fps; +} + +function animate() { + if (!paused) { + eraseBackground(); + drawBackground(); + update(); + draw(); + + window.requestNextAnimationFrame(animate); + } +} + +context.font = '48px Helvetica'; + +canvas.onclick = function(e) { + paused = paused ? false : true; +}; + +context.canvas.width = canvas.width; +context.canvas.height = canvas.height; + +animateButton.onclick = function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + window.requestNextAnimationFrame(animate); + } + else { + animateButton.value = 'Pause'; + } +}; + +context.font = '48px Helvetica'; +drawBackground(); + + + + +/* + * Copyright (C) 2011 David Geary + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + paused = true, + discs = [ + { + x: 150, + y: 250, + lastX: 150, + lastY: 250, + velocityX: -3.2, + velocityY: 3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 150, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + strokeStyle: 'blue' + }, + + { + x: 150, + y: 75, + lastX: 150, + lastY: 75, + velocityX: 1.2, + velocityY: 1.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + strokeStyle: 'orange' + }, + ], + numDiscs = discs.length, + animateButton = document.getElementById('animateButton'); + +// Functions..................................................... + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'lightgray'; + context.lineWidth = 0.5; + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function update() { + var disc = null; + + for(var i=0; i < numDiscs; ++i) { + disc = discs[i]; + + if (disc.x + disc.velocityX + disc.radius > context.canvas.width || + disc.x + disc.velocityX - disc.radius < 0) + disc.velocityX = -disc.velocityX; + + if (disc.y + disc.velocityY + disc.radius > context.canvas.height || + disc.y + disc.velocityY - disc.radius < 0) + disc.velocityY= -disc.velocityY; + + disc.x += disc.velocityX; + disc.y += disc.velocityY; + } +} + +function draw() { + var disc = discs[i]; + + for(var i=0; i < numDiscs; ++i) { + disc = discs[i]; + + gradient = context.createRadialGradient(disc.x, disc.y, 0, + disc.x, disc.y, disc.radius); + + gradient.addColorStop(0.3, disc.innerColor); + gradient.addColorStop(0.5, disc.middleColor); + gradient.addColorStop(1.0, disc.outerColor); + + context.save(); + context.beginPath(); + context.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false); + context.fillStyle = gradient; + context.strokeStyle = disc.strokeStyle; + context.fill(); + context.stroke(); + context.restore(); + } +} + +// Animation..................................................... + +function animate(time) { + if (!paused) { + context.clearRect(0,0,canvas.width,canvas.height); + drawBackground(); + update(); + draw(); + + context.fillStyle = 'cornflowerblue'; + context.fillText(calculateFps().toFixed() + ' fps', 45, 50); + + window.requestNextAnimationFrame(animate); + } +} + +// Initialization................................................ + +context.font = '48px Helvetica'; + +animateButton.onclick = function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + } + else { + window.requestNextAnimationFrame(animate); + animateButton.value = 'Pause'; + } +}; diff --git a/canvas/ch05/example-5.12/example.html b/canvas/ch05/example-5.12/example.html new file mode 100644 index 0000000..c97c0f2 --- /dev/null +++ b/canvas/ch05/example-5.12/example.html @@ -0,0 +1,69 @@ + + + + + Scheduling Tasks + + + + + +
+ +
+ + + Canvas not supported + + + + + diff --git a/canvas/ch05/example-5.12/example.js b/canvas/ch05/example-5.12/example.js new file mode 100644 index 0000000..ad10021 --- /dev/null +++ b/canvas/ch05/example-5.12/example.js @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.querySelector('#canvas'), + context = canvas.getContext('2d'), + paused = true, + discs = [ + { + x: 150, + y: 50, + lastX: 150, + lastY: 50, + velocityX: 3.2, + velocityY: 3.5, + radius: 25, + innerColor: 'rgba(0,255,255,0.3)', + middleColor: 'rgba(0,255,255,0.9)', + outerColor: 'rgba(0,255,255,0.3)', + strokeStyle: 'slateblue', + }, + + { + x: 75, + y: 200, + lastX: 75, + lastY: 200, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(225,225,225,0.1)', + middleColor: 'rgba(225,225,225,0.9)', + outerColor: 'rgba(225,225,225,0.3)', + strokeStyle: 'gray' + }, + + { + x: 100, + y: 300, + lastX: 150, + lastY: 50, + velocityX: 1.2, + velocityY: 1.5, + radius: 25, + innerColor: 'orange', + middleColor: 'yellow', + outerColor: 'gold', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + ], + numDiscs = discs.length, + lastTime = 0, + lastFpsUpdateTime = 0, + frameCount = 0, + animateButton = document.querySelector('#animateButton'); + +function eraseBackground() { + context.clearRect(0,0,canvas.width,canvas.height); +} + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'lightgray'; + context.lineWidth = 0.5; + + context.save(); + context.restore(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function update() { + var i = numDiscs, + disc = null; + + while(i--) { + disc = discs[i]; + + if (disc.x + disc.velocityX + disc.radius > context.canvas.width || + disc.x + disc.velocityX - disc.radius < 0) + disc.velocityX = -disc.velocityX; + + if (disc.y + disc.velocityY + disc.radius > context.canvas.height || + disc.y + disc.velocityY - disc.radius < 0) + disc.velocityY= -disc.velocityY; + + disc.x += disc.velocityX; + disc.y += disc.velocityY; + } +} + +function drawDisc(disc) { + var gradient = context.createRadialGradient(disc.x, disc.y, 0, + disc.x, disc.y, disc.radius); + + gradient.addColorStop(0.3, disc.innerColor); + gradient.addColorStop(0.7, disc.middleColor); + gradient.addColorStop(1.0, disc.outerColor); + + context.save(); + context.beginPath(); + context.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false); + context.clip(); + + context.fillStyle = gradient; + context.strokeStyle = disc.strokeStyle; + context.lineWidth = 2; + context.fill(); + context.stroke(); + + context.restore(); +} + +function draw() { + var i = numDiscs, disc; + + i = numDiscs; + while(i--) { + disc = discs[i]; + drawDisc(disc); + disc.lastX = disc.x; + disc.lastY = disc.y; + } + + if (frameCount === 100) { + frameCount = -1; + } + + if (frameCount !== -1 && frameCount < 100) { + frameCount++; + } +} + +function calculateFps() { + var now = (+new Date), + fps = 1000 / (now - lastTime); + + lastTime = now; + + return fps; +} + +function animate() { + var now = (+new Date), + fps = 0; + + if (!paused) { + eraseBackground(); + drawBackground(); + update(); + draw(); + + fps = calculateFps(); + + if (now - lastFpsUpdateTime > 1000) { + lastFpsUpdateTime = now; + lastFpsUpdate = fps; + } + context.fillStyle = 'cornflowerblue'; + context.fillText(lastFpsUpdate.toFixed() + ' fps', 45, 50); + } + if (window.webkitRequestAnimationFrame !== undefined) { + window.webkitRequestAnimationFrame(animate); + } + else if (window.mozRequestAnimationFrame !== undefined) { + window.mozRequestAnimationFrame(animate); + } +} + +context.font = '48px Helvetica'; + +if (window.webkitRequestAnimationFrame !== undefined) { + window.webkitRequestAnimationFrame(animate); +} +else if (window.mozRequestAnimationFrame !== undefined) { + window.mozRequestAnimationFrame(animate); +} +else { + setInterval(animate, 1000/60); +} + +canvas.onclick = function(e) { + paused = paused ? false : true; +}; + +context.canvas.width = canvas.width; +context.canvas.height = canvas.height; + +animateButton.onclick = function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + } + else { + animateButton.value = 'Pause'; + } +}; + +context.font = '48px Helvetica'; +drawBackground(); diff --git a/canvas/ch05/example-5.14/bottom.js b/canvas/ch05/example-5.14/bottom.js new file mode 100644 index 0000000..87d75d4 --- /dev/null +++ b/canvas/ch05/example-5.14/bottom.js @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {}; + +COREHTML5.bottomRightTimeBasedMotion = function () { +var canvas = document.querySelector('#bottomCanvas'), + context = canvas.getContext('2d'), + paused = true, + discs = [ + { + x: 150, + y: 250, + lastX: 150, + lastY: 250, + velocityX: -3.2, + velocityY: 3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 150, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 150, + y: 75, + lastX: 150, + lastY: 75, + velocityX: 1.2, + velocityY: 1.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 50, + y: 150, + lastX: 150, + lastY: 250, + velocityX: -3.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 200, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -1.9, + velocityY: 1.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 250, + lastX: 150, + lastY: 250, + velocityX: 5.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 100, + lastX: 50, + lastY: 150, + velocityX: -2.9, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 215, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 150, + lastX: 150, + lastY: 250, + velocityX: 4.2, + velocityY: -5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 3.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 100, + y: 100, + lastX: 150, + lastY: 75, + velocityX: -2.9, + velocityY: -2.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 150, + y: 250, + lastX: 150, + lastY: 250, + velocityX: -5.2, + velocityY: 5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 150, + lastX: 50, + lastY: 150, + velocityX: 4.2, + velocityY: 4.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 150, + y: 75, + lastX: 150, + lastY: 75, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 50, + y: 150, + lastX: 150, + lastY: 250, + velocityX: -0.2, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 1.2, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 200, + y: 175, + lastX: 150, + lastY: 75, + velocityX: 1.9, + velocityY: -1.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 250, + lastX: 150, + lastY: 250, + velocityX: 3.2, + velocityY: -5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 100, + lastX: 50, + lastY: 150, + velocityX: -1.9, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 215, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -3.2, + velocityY: 4.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 150, + lastX: 150, + lastY: 250, + velocityX: 2.2, + velocityY: -4.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 100, + y: 100, + lastX: 150, + lastY: 75, + velocityX: -5.9, + velocityY: -0.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + ], + numDiscs = discs.length, + startTime = 0, + lastTime = 0, + fps = 0, + lastFpsUpdate = { time: 0, value: 0 }, + animateButton = document.querySelector('#bottomAnimateButton'), + timeBasedMotionCheckbox = document.querySelector('#timeBasedMotionCheckbox'), + timeBasedMotion = timeBasedMotionCheckbox.checked; + +function eraseBackground() { + context.clearRect(0,0,canvas.width,canvas.height); +} + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'lightgray'; + context.lineWidth = 0.5; + + context.save(); + context.restore(); + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function update() { + var i = numDiscs, + disc = null; + + while(i--) { + disc = discs[i]; + + if (disc.x + disc.velocityX + disc.radius > context.canvas.width || + disc.x + disc.velocityX - disc.radius < 0) + disc.velocityX = -disc.velocityX; + + if (disc.y + disc.velocityY + disc.radius > context.canvas.height || + disc.y + disc.velocityY - disc.radius < 0) + disc.velocityY= -disc.velocityY; + + disc.x += disc.velocityX; + disc.y += disc.velocityY; + } +} + +function updateTimeBased(time) { + var i = numDiscs, + disc = null; + + if (fps == 0) + return; + + while(i--) { + disc = discs[i]; + deltaX = disc.velocityX / fps; + deltaY = disc.velocityY / fps; + + if (disc.x + deltaX + disc.radius > context.canvas.width || + disc.x + deltaX - disc.radius < 0) { + disc.velocityX = -disc.velocityX; + deltaX = -deltaX; + } + + if (disc.y + deltaY + disc.radius > context.canvas.height || + disc.y + deltaY - disc.radius < 0) { + disc.velocityY= -disc.velocityY; + deltaX = -deltaX; + } + + disc.x = disc.x + deltaX; + disc.y = disc.y + deltaY; + } +} + +function draw() { + var i = numDiscs, + disc = discs[i]; + + while(i--) { + disc = discs[i]; + + gradient = context.createRadialGradient(disc.x, disc.y, 0, + disc.x, disc.y, disc.radius); + + gradient.addColorStop(0.3, disc.innerColor); + gradient.addColorStop(0.5, disc.middleColor); + gradient.addColorStop(1.0, disc.outerColor); + + context.beginPath(); + context.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false); + + context.save(); + + context.fillStyle = gradient; + context.strokeStyle = disc.strokeStyle; + context.fill(); + context.stroke(); + context.restore(); + } +} + +function calculateFps(now) { + fps = 1000 / (now - lastTime); + lastTime = now; +} + +function updateFps() { + var now = (+new Date); + + calculateFps(now); + + if (now - startTime < 2000) { + return; + } + + if (now - lastFpsUpdate.time > 1000) { + lastFpsUpdate.time = now; + lastFpsUpdate.value = fps; + } + if (!paused) { + context.fillStyle = 'cornflowerblue'; + context.fillText(lastFpsUpdate.value.toFixed() + ' fps', 50, 48); + } +} + +function animate(time) { + if (time === undefined) { + time = +new Date; + } + + if (!paused) { + eraseBackground(); + drawBackground(); + if (timeBasedMotion) { + updateTimeBased(time); + } + else { + update(); + } + draw(); + } + + updateFps(); +} + +animateButton.addEventListener('click', function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + } + else { + animateButton.value = 'Pause'; + } +}); + +timeBasedMotionCheckbox.addEventListener('click', function (e) { + if (timeBasedMotionCheckbox.checked) { + timeBasedMotion = true; + for (var i=0; i < discs.length; ++i) { + discs[i].velocityX *= 50; + discs[i].velocityY *= 50; + } + } + else { + timeBasedMotion = false; + for (var i=0; i < discs.length; ++i) { + discs[i].velocityX /= 50; + discs[i].velocityY /= 50; + } + } +}); + +context.font = '36px Helvetica'; +drawBackground(); +startTime = +new Date; +setInterval(animate, 1000/60); +}(); diff --git a/canvas/ch05/example-5.14/example.html b/canvas/ch05/example-5.14/example.html new file mode 100644 index 0000000..530a298 --- /dev/null +++ b/canvas/ch05/example-5.14/example.html @@ -0,0 +1,106 @@ + + + + + Time-based Motion + + + + + +
+
+ Time-based Motion  +
+ + + + +
+ + + Canvas not supported + + + + Canvas not supported + + + + + + diff --git a/canvas/ch05/example-5.14/example2.html b/canvas/ch05/example-5.14/example2.html new file mode 100644 index 0000000..c92cddf --- /dev/null +++ b/canvas/ch05/example-5.14/example2.html @@ -0,0 +1,99 @@ + + + + + Time-based Motion + + + + + +
+
+ Time-based Motion  +
+ + + +
+ + + Canvas not supported + + + + + diff --git a/canvas/ch05/example-5.14/example2.js b/canvas/ch05/example-5.14/example2.js new file mode 100644 index 0000000..e446aa1 --- /dev/null +++ b/canvas/ch05/example-5.14/example2.js @@ -0,0 +1,592 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {}; + +COREHTML5.topLeftTimeBasedMotion = function () { +var topCanvas = document.querySelector('#topCanvas'), + topContext = topCanvas.getContext('2d'), + paused = true, + discs = [ + { + x: 150, + y: 250, + lastX: 150, + lastY: 250, + velocityX: -3.2, + velocityY: 3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 150, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 150, + y: 75, + lastX: 150, + lastY: 75, + velocityX: 1.2, + velocityY: 1.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 50, + y: 150, + lastX: 150, + lastY: 250, + velocityX: -3.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 200, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -1.9, + velocityY: 1.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 250, + lastX: 150, + lastY: 250, + velocityX: 5.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 100, + lastX: 50, + lastY: 150, + velocityX: -2.9, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 215, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 150, + lastX: 150, + lastY: 250, + velocityX: 4.2, + velocityY: -5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 3.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 100, + y: 100, + lastX: 150, + lastY: 75, + velocityX: -2.9, + velocityY: -2.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 150, + y: 250, + lastX: 150, + lastY: 250, + velocityX: -5.2, + velocityY: 5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 150, + lastX: 50, + lastY: 150, + velocityX: 4.2, + velocityY: 4.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 150, + y: 75, + lastX: 150, + lastY: 75, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 50, + y: 150, + lastX: 150, + lastY: 250, + velocityX: -0.2, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 1.2, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 200, + y: 175, + lastX: 150, + lastY: 75, + velocityX: 1.9, + velocityY: -1.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 250, + lastX: 150, + lastY: 250, + velocityX: 3.2, + velocityY: -5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 100, + lastX: 50, + lastY: 150, + velocityX: -1.9, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 215, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -3.2, + velocityY: 4.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 150, + lastX: 150, + lastY: 250, + velocityX: 2.2, + velocityY: -4.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 100, + y: 100, + lastX: 150, + lastY: 75, + velocityX: -5.9, + velocityY: -0.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + ], + numDiscs = discs.length, + startTime = 0, + lastTime = 0, + elapsedTime = 0, + fps = 0, + lastFpsUpdate = { time: 0, value: 0 }, + animateButton = document.querySelector('#topAnimateButton'), + timeBasedMotionCheckbox = document.querySelector('#timeBasedMotionCheckbox'), + timeBasedMotion = timeBasedMotionCheckbox.checked; + +function eraseBackground() { + topContext.clearRect(0,0,topCanvas.width,topCanvas.height); +} + +function drawBackground() { + var STEP_Y = 12, + i = topContext.canvas.height; + + topContext.strokeStyle = 'lightgray'; + topContext.lineWidth = 0.5; + + topContext.save(); + topContext.restore(); + + while(i > STEP_Y*4) { + topContext.beginPath(); + topContext.moveTo(0, i); + topContext.lineTo(topContext.canvas.width, i); + topContext.stroke(); + i -= STEP_Y; + } + + topContext.save(); + + topContext.strokeStyle = 'rgba(100,0,0,0.3)'; + topContext.lineWidth = 1; + + topContext.beginPath(); + + topContext.moveTo(35,0); + topContext.lineTo(35,topContext.canvas.height); + topContext.stroke(); + + topContext.restore(); +} + +function update() { + var i = numDiscs, + disc = null; + + while(i--) { + disc = discs[i]; + + if (disc.x + disc.velocityX + disc.radius > topContext.canvas.width || + disc.x + disc.velocityX - disc.radius < 0) + disc.velocityX = -disc.velocityX; + + if (disc.y + disc.velocityY + disc.radius > topContext.canvas.height || + disc.y + disc.velocityY - disc.radius < 0) + disc.velocityY= -disc.velocityY; + + disc.x += disc.velocityX; + disc.y += disc.velocityY; + } +} + +function updateTimeBased(time) { + var i = numDiscs, + disc = null; + + if (fps == 0) + return; + + while(i--) { + disc = discs[i]; + deltaX = disc.velocityX + + deltaX = disc.velocityX * (elapsedTime / 1000); + deltaY = disc.velocityY * (elapsedTime / 1000); + + if (disc.x + deltaX + disc.radius > topContext.canvas.width || + disc.x + deltaX - disc.radius < 0) { + disc.velocityX = -disc.velocityX; + deltaX = -deltaX; + } + + if (disc.y + deltaY + disc.radius > topContext.canvas.height || + disc.y + deltaY - disc.radius < 0) { + disc.velocityY= -disc.velocityY; + deltaY = -deltaY; + } + + disc.x = disc.x + deltaX; + disc.y = disc.y + deltaY; + } +} + +function draw() { + var i = numDiscs, + disc = discs[i]; + + while(i--) { + disc = discs[i]; + + gradient = topContext.createRadialGradient(disc.x, disc.y, 0, + disc.x, disc.y, disc.radius); + + gradient.addColorStop(0.3, disc.innerColor); + gradient.addColorStop(0.5, disc.middleColor); + gradient.addColorStop(1.0, disc.outerColor); + + topContext.beginPath(); + topContext.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false); + + topContext.save(); + + topContext.fillStyle = gradient; + topContext.strokeStyle = disc.strokeStyle; + topContext.fill(); + topContext.stroke(); + topContext.restore(); + } +} + +function calculateFps(now) { + elapsedTime = now - lastTime; + fps = 1000 / elapsedTime; + lastTime = now; +} + +function updateFps() { + var now = (+new Date); + + calculateFps(now); + + if (now - startTime < 2000) { + return; + } + + if (now - lastFpsUpdate.time > 1000) { + lastFpsUpdate.time = now; + lastFpsUpdate.value = fps; + } + if (!paused) { + topContext.fillStyle = 'cornflowerblue'; + topContext.fillText(lastFpsUpdate.value.toFixed() + ' fps', 50, 48); + } +} + +function animateTopLeft(time) { + if (time === undefined) { + time = +new Date; + } + + if (!paused) { + eraseBackground(); + drawBackground(); + if (timeBasedMotion) { + updateTimeBased(time); + } + else { + update(); + } + draw(); + } + + updateFps(); +} + +animateButton.addEventListener('click', function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + } + else { + animateButton.value = 'Pause'; + } +}); + +timeBasedMotionCheckbox.addEventListener('click', function (e) { + if (timeBasedMotionCheckbox.checked) { + timeBasedMotion = true; + for (var i=0; i < discs.length; ++i) { + discs[i].velocityX *= 50; + discs[i].velocityY *= 50; + } + } + else { + timeBasedMotion = false; + for (var i=0; i < discs.length; ++i) { + discs[i].velocityX /= 50; + discs[i].velocityY /= 50; + } + } +}); + +topContext.font = '36px Helvetica'; +drawBackground(); +startTime = +new Date; +setInterval(animateTopLeft, 1000/120); +}(); diff --git a/canvas/ch05/example-5.14/top.js b/canvas/ch05/example-5.14/top.js new file mode 100644 index 0000000..93d3953 --- /dev/null +++ b/canvas/ch05/example-5.14/top.js @@ -0,0 +1,592 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {}; + +COREHTML5.topLeftTimeBasedMotion = function () { +var topCanvas = document.querySelector('#topCanvas'), + topContext = topCanvas.getContext('2d'), + paused = true, + discs = [ + { + x: 150, + y: 250, + lastX: 150, + lastY: 250, + velocityX: -3.2, + velocityY: 3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 150, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 150, + y: 75, + lastX: 150, + lastY: 75, + velocityX: 1.2, + velocityY: 1.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 50, + y: 150, + lastX: 150, + lastY: 250, + velocityX: -3.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 200, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -1.9, + velocityY: 1.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 250, + lastX: 150, + lastY: 250, + velocityX: 5.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 100, + lastX: 50, + lastY: 150, + velocityX: -2.9, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 215, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 150, + lastX: 150, + lastY: 250, + velocityX: 4.2, + velocityY: -5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 3.2, + velocityY: -3.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 100, + y: 100, + lastX: 150, + lastY: 75, + velocityX: -2.9, + velocityY: -2.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 150, + y: 250, + lastX: 150, + lastY: 250, + velocityX: -5.2, + velocityY: 5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 150, + lastX: 50, + lastY: 150, + velocityX: 4.2, + velocityY: 4.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 150, + y: 75, + lastX: 150, + lastY: 75, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 50, + y: 150, + lastX: 150, + lastY: 250, + velocityX: -0.2, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 1.2, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 200, + y: 175, + lastX: 150, + lastY: 75, + velocityX: 1.9, + velocityY: -1.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 250, + lastX: 150, + lastY: 250, + velocityX: 3.2, + velocityY: -5.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 100, + lastX: 50, + lastY: 150, + velocityX: -1.9, + velocityY: -2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 215, + y: 175, + lastX: 150, + lastY: 75, + velocityX: -3.2, + velocityY: 4.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + { + x: 250, + y: 150, + lastX: 150, + lastY: 250, + velocityX: 2.2, + velocityY: -4.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + shadowColor: 'rgba(175,175,175,0.7)', + strokeStyle: 'gray', + }, + + { + x: 150, + y: 75, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: -1.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + shadowColor: 'rgba(100,145,230,0.8)', + strokeStyle: 'blue' + }, + + { + x: 100, + y: 100, + lastX: 150, + lastY: 75, + velocityX: -5.9, + velocityY: -0.2, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + shadowColor: 'rgba(255,0,0,0.7)', + strokeStyle: 'orange' + }, + ], + numDiscs = discs.length, + startTime = 0, + lastTime = 0, + elapsedTime = 0, + fps = 0, + lastFpsUpdate = { time: 0, value: 0 }, + animateButton = document.querySelector('#topAnimateButton'), + timeBasedMotionCheckbox = document.querySelector('#timeBasedMotionCheckbox'), + timeBasedMotion = timeBasedMotionCheckbox.checked; + +function eraseBackground() { + topContext.clearRect(0,0,topCanvas.width,topCanvas.height); +} + +function drawBackground() { + var STEP_Y = 12, + i = topContext.canvas.height; + + topContext.strokeStyle = 'lightgray'; + topContext.lineWidth = 0.5; + + topContext.save(); + topContext.restore(); + + while(i > STEP_Y*4) { + topContext.beginPath(); + topContext.moveTo(0, i); + topContext.lineTo(topContext.canvas.width, i); + topContext.stroke(); + i -= STEP_Y; + } + + topContext.save(); + + topContext.strokeStyle = 'rgba(100,0,0,0.3)'; + topContext.lineWidth = 1; + + topContext.beginPath(); + + topContext.moveTo(35,0); + topContext.lineTo(35,topContext.canvas.height); + topContext.stroke(); + + topContext.restore(); +} + +function update() { + var i = numDiscs, + disc = null; + + while(i--) { + disc = discs[i]; + + if (disc.x + disc.velocityX + disc.radius > topContext.canvas.width || + disc.x + disc.velocityX - disc.radius < 0) + disc.velocityX = -disc.velocityX; + + if (disc.y + disc.velocityY + disc.radius > topContext.canvas.height || + disc.y + disc.velocityY - disc.radius < 0) + disc.velocityY= -disc.velocityY; + + disc.x += disc.velocityX; + disc.y += disc.velocityY; + } +} + +function updateTimeBased(time) { + var i = numDiscs, + disc = null; + + if (fps == 0) + return; + + while(i--) { + disc = discs[i]; + deltaX = disc.velocityX + + deltaX = disc.velocityX * (elapsedTime / 1000); + deltaY = disc.velocityY * (elapsedTime / 1000); + + if (disc.x + deltaX + disc.radius > topContext.canvas.width || + disc.x + deltaX - disc.radius < 0) { + disc.velocityX = -disc.velocityX; + deltaX = -deltaX; + } + + if (disc.y + deltaY + disc.radius > topContext.canvas.height || + disc.y + deltaY - disc.radius < 0) { + disc.velocityY= -disc.velocityY; + deltaY = -deltaY; + } + + disc.x = disc.x + deltaX; + disc.y = disc.y + deltaY; + } +} + +function draw() { + var i = numDiscs, + disc = discs[i]; + + while(i--) { + disc = discs[i]; + + gradient = topContext.createRadialGradient(disc.x, disc.y, 0, + disc.x, disc.y, disc.radius); + + gradient.addColorStop(0.3, disc.innerColor); + gradient.addColorStop(0.5, disc.middleColor); + gradient.addColorStop(1.0, disc.outerColor); + + topContext.beginPath(); + topContext.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false); + + topContext.save(); + + topContext.fillStyle = gradient; + topContext.strokeStyle = disc.strokeStyle; + topContext.fill(); + topContext.stroke(); + topContext.restore(); + } +} + +function calculateFps(now) { + elapsedTime = now - lastTime; + fps = 1000 / elapsedTime; + lastTime = now; +} + +function updateFps() { + var now = (+new Date); + + calculateFps(now); + + if (now - startTime < 2000) { + return; + } + + if (now - lastFpsUpdate.time > 1000) { + lastFpsUpdate.time = now; + lastFpsUpdate.value = fps; + } + if (!paused) { + topContext.fillStyle = 'cornflowerblue'; + topContext.fillText(lastFpsUpdate.value.toFixed() + ' fps', 50, 48); + } +} + +function animateTopLeft(time) { + if (time === undefined) { + time = +new Date; + } + + if (!paused) { + eraseBackground(); + drawBackground(); + if (timeBasedMotion) { + updateTimeBased(time); + } + else { + update(); + } + draw(); + } + + updateFps(); +} + +animateButton.addEventListener('click', function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + } + else { + animateButton.value = 'Pause'; + } +}); + +timeBasedMotionCheckbox.addEventListener('click', function (e) { + if (timeBasedMotionCheckbox.checked) { + timeBasedMotion = true; + for (var i=0; i < discs.length; ++i) { + discs[i].velocityX *= 50; + discs[i].velocityY *= 50; + } + } + else { + timeBasedMotion = false; + for (var i=0; i < discs.length; ++i) { + discs[i].velocityX /= 50; + discs[i].velocityY /= 50; + } + } +}); + +topContext.font = '36px Helvetica'; +drawBackground(); +startTime = +new Date; +setInterval(animateTopLeft, 1000/60); +}(); diff --git a/canvas/ch05/example-5.15/example.html b/canvas/ch05/example-5.15/example.html new file mode 100644 index 0000000..00e321a --- /dev/null +++ b/canvas/ch05/example-5.15/example.html @@ -0,0 +1,71 @@ + + + + + Scrolling Backgrounds + + + + + + + Canvas not supported + + + + + + + + diff --git a/canvas/ch05/example-5.15/example.js b/canvas/ch05/example-5.15/example.js new file mode 100644 index 0000000..9d33290 --- /dev/null +++ b/canvas/ch05/example-5.15/example.js @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + controls = document.getElementById('controls'), + animateButton = document.getElementById('animateButton'), + sky = new Image(), + + paused = true, + lastTime = 0, + fps = 0, + + skyOffset = 0, + SKY_VELOCITY = 30; // 30 pixels/second + +// Functions..................................................... + +function erase() { + context.clearRect(0,0,canvas.width,canvas.height); +} + +function draw() { + context.save(); + + skyOffset = skyOffset < canvas.width ? + skyOffset + SKY_VELOCITY/fps : 0; + + context.save(); + context.translate(-skyOffset, 0); + + context.drawImage(sky, 0, 0); + context.drawImage(sky, sky.width-2, 0); + + context.restore(); +} + +function calculateFps(now) { + var fps = 1000 / (now - lastTime); + lastTime = now; + return fps; +} + +function animate(now) { + if (now === undefined) { + now = +new Date; + } + + fps = calculateFps(now); + + if (!paused) { + erase(); + draw(); + } + + requestNextAnimationFrame(animate); +} + +// Event handlers................................................ + +animateButton.onclick = function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + } + else { + animateButton.value = 'Pause'; + } +}; + +// Initialization................................................ + +canvas.width = canvas.width; +canvas.height = canvas.height; + +sky.src = '../../shared/images/sky.png'; +sky.onload = function (e) { + draw(); +}; + +requestNextAnimationFrame(animate); diff --git a/canvas/ch05/example-5.17/example.html b/canvas/ch05/example-5.17/example.html new file mode 100644 index 0000000..61531d0 --- /dev/null +++ b/canvas/ch05/example-5.17/example.html @@ -0,0 +1,70 @@ + + + + + Parallax + + + + + + + + + Canvas not supported + + + + + + diff --git a/canvas/ch05/example-5.17/example.js b/canvas/ch05/example-5.17/example.js new file mode 100644 index 0000000..8432cc4 --- /dev/null +++ b/canvas/ch05/example-5.17/example.js @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + controls = document.getElementById('controls'), + animateButton = document.getElementById('animateButton'), + + tree = new Image(), + nearTree = new Image(), + grass = new Image(), + grass2 = new Image(), + sky = new Image(), + + paused = true, + lastTime = 0, + lastFpsUpdate = { time: 0, value: 0 }, + fps=60, + + skyOffset = 0, + grassOffset = 0, + treeOffset = 0, + nearTreeOffset = 0, + + TREE_VELOCITY = 20, + FAST_TREE_VELOCITY = 40, + SKY_VELOCITY = 8, + GRASS_VELOCITY = 75; + +// Functions..................................................... + +function erase() { + context.clearRect(0,0,canvas.width,canvas.height); +} + +function draw() { + context.save(); + + skyOffset = skyOffset < canvas.width ? + skyOffset + SKY_VELOCITY/fps : 0; + + grassOffset = grassOffset < canvas.width ? + grassOffset + GRASS_VELOCITY/fps : 0; + + treeOffset = treeOffset < canvas.width ? + treeOffset + TREE_VELOCITY/fps : 0; + + nearTreeOffset = nearTreeOffset < canvas.width ? + nearTreeOffset + FAST_TREE_VELOCITY/fps : 0; + + context.save(); + context.translate(-skyOffset, 0); + context.drawImage(sky, 0, 0); + context.drawImage(sky, sky.width-2, 0); + context.restore(); + + context.save(); + context.translate(-treeOffset, 0); + context.drawImage(tree, 100, 240); + context.drawImage(tree, 1100, 240); + context.drawImage(tree, 400, 240); + context.drawImage(tree, 1400, 240); + context.drawImage(tree, 700, 240); + context.drawImage(tree, 1700, 240); + context.restore(); + + context.save(); + context.translate(-nearTreeOffset, 0); + context.drawImage(nearTree, 250, 220); + context.drawImage(nearTree, 1250, 220); + context.drawImage(nearTree, 800, 220); + context.drawImage(nearTree, 1800, 220); + context.restore(); + + context.save(); + context.translate(-grassOffset, 0); + + context.drawImage(grass, 0, canvas.height-grass.height); + + context.drawImage(grass, grass.width-5, + canvas.height-grass.height); + + context.drawImage(grass2, 0, canvas.height-grass2.height); + + context.drawImage(grass2, grass2.width, + canvas.height-grass2.height); + context.restore(); + +} + +function calculateFps(now) { + var fps = 1000 / (now - lastTime); + lastTime = now; + return fps; +} + +function animate(now) { + if (now === undefined) { + now = +new Date; + } + + fps = calculateFps(now); + + if (!paused) { + erase(); + draw(); + } + + requestNextAnimationFrame(animate); +} + +// Event handlers................................................ + +animateButton.onclick = function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + } + else { + animateButton.value = 'Pause'; + } +}; + +// Initialization................................................ + +context.font = '48px Helvetica'; + +tree.src = '../../shared/images/smalltree.png'; +nearTree.src = '../../shared/images/tree-twotrunks.png'; +grass.src = '../../shared/images/grass.png'; +grass2.src = '../../shared/images/grass2.png'; +sky.src = '../../shared/images/sky.png'; + +sky.onload = function (e) { + draw(); +}; + +requestNextAnimationFrame(animate); diff --git a/canvas/ch05/example-5.18/example.html b/canvas/ch05/example-5.18/example.html new file mode 100644 index 0000000..e337f5d --- /dev/null +++ b/canvas/ch05/example-5.18/example.html @@ -0,0 +1,186 @@ + + + + + + Magnifying Glass + + + + +
+
+ 1.5 +
+ + + Canvas not supported + +
+ +
+ + + Canvas not supported + +
+ + + + + + diff --git a/canvas/ch05/example-5.18/example.js b/canvas/ch05/example-5.18/example.js new file mode 100644 index 0000000..9f6f444 --- /dev/null +++ b/canvas/ch05/example-5.18/example.js @@ -0,0 +1,553 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + image = new Image(), + imageData = null, + dragging = false, + + glassSizeCanvas = document.getElementById('glassSizeCanvas'), + glassSizeContext = glassSizeCanvas.getContext('2d'), + + MAXIMUM_SCALE = 4.0, + scaleOutput = document.getElementById('scaleOutput'), + + magnifyingGlassRadius = 120, + magnificationScale = scaleOutput.innerHTML, + magnifyRectangle = {}, + + MAX_GLASS_RADIUS = 350, + + magnifyingGlassX = 512, + magnifyingGlassY = 340, + + magnifyZoomSlider = new COREHTML5.Slider('rgb(72,92,55)', // stroke + 'rgb(246, 201, 204)', // filjl + 0.25, // knob percent + 90, // take up % of width + 55), // take up % of height + + glassSlider = new COREHTML5.Slider('rgb(72,92,55)', + 'rgb(246, 201, 204)', + 0.50, 90, 55), + animating = false, + animationLoop = null, + + mousedown = null, + mouseup = null, + + canvasRatio = canvas.height / canvas.width, + pinchRatio; + +// Functions................................................... + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function calculateMagnifyRectangle(mouse) { + var top, + left, + bottom, + right; + + magnifyRectangle.x = mouse.x - magnifyingGlassRadius; + magnifyRectangle.y = mouse.y - magnifyingGlassRadius; + magnifyRectangle.width = magnifyingGlassRadius*2 + 2*context.lineWidth; + magnifyRectangle.height = magnifyingGlassRadius*2 + 2*context.lineWidth; + + top = magnifyRectangle.y; + left = magnifyRectangle.x; + bottom = magnifyRectangle.y + magnifyRectangle.height; + right = magnifyRectangle.x + magnifyRectangle.width; + + if (left < 0) { + magnifyRectangle.width += left; + magnifyRectangle.x = 0; + } + else if (right > canvas.width) { + magnifyRectangle.width -= right - canvas.width; + } + + if (top < 0) { + magnifyRectangle.height += magnifyRectangle.y; + magnifyRectangle.y = 0; + } + else if (bottom > canvas.height) { + magnifyRectangle.height -= bottom - canvas.height; + } +} + +function setClip() { + context.beginPath(); + context.arc(magnifyingGlassX, magnifyingGlassY, + magnifyingGlassRadius, 0, Math.PI*2, false); + context.clip(); +} + +function drawMagnifyingGlassCircle(mouse) { + var gradientThickness = this.magnifyingGlassRadius / 7; + + gradientThickness = gradientThickness < 10 ? 10 : gradientThickness; + gradientThickness = gradientThickness > 40 ? 40 : gradientThickness; + + gradientThickness = 10; + this.context.save(); + this.context.lineWidth = gradientThickness; + this.context.strokeStyle = 'rgb(0, 0, 255, 0.3)'; + + this.context.beginPath(); + this.context.arc(mouse.x, mouse.y, + this.magnifyingGlassRadius, 0, Math.PI*2, false); + this.context.clip(); + + var gradient = this.context.createRadialGradient( + mouse.x, mouse.y, this.magnifyingGlassRadius-gradientThickness, + mouse.x, mouse.y, this.magnifyingGlassRadius); + gradient.addColorStop(0, 'rgba(0,0,0,0.2)'); + gradient.addColorStop(0.80, 'rgb(235,237,255)'); + gradient.addColorStop(0.90, 'rgb(235,237,255)'); + gradient.addColorStop(1.0, 'rgba(150,150,150,0.9)'); + + this.context.shadowColor = 'rgba(52, 72, 35, 1.0)'; + this.context.shadowOffsetX = 2; + this.context.shadowOffsetY = 2; + this.context.shadowBlur = 20; + + this.context.strokeStyle = gradient; + this.context.stroke(); + + this.context.beginPath(); + this.context.arc(mouse.x, mouse.y, + this.magnifyingGlassRadius-gradientThickness/2, 0, Math.PI*2, false); + this.context.clip(); + + this.context.lineWidth = gradientThickness; + this.context.strokeStyle = 'rgba(0,0,0,0.06)'; + this.context.stroke(); + + this.context.restore(); +}; + +function drawMagnifyingGlass(mouse) { + var scaledMagnifyRectangle; + + magnifyingGlassX = mouse.x; + magnifyingGlassY = mouse.y; + + calculateMagnifyRectangle(mouse); + + imageData = context.getImageData(magnifyRectangle.x, + magnifyRectangle.y, + magnifyRectangle.width, + magnifyRectangle.height); + context.save(); + + scaledMagnifyRectangle = { + width: magnifyRectangle.width * magnificationScale, + height: magnifyRectangle.height * magnificationScale + }; + + setClip(); + + context.drawImage(canvas, + magnifyRectangle.x, magnifyRectangle.y, + magnifyRectangle.width, magnifyRectangle.height, + + magnifyRectangle.x + magnifyRectangle.width/2 - + scaledMagnifyRectangle.width/2, + + magnifyRectangle.y + magnifyRectangle.height/2 - + scaledMagnifyRectangle.height/2, + + scaledMagnifyRectangle.width, + scaledMagnifyRectangle.height); + + context.restore(); + + drawMagnifyingGlassCircle(mouse); +} + +function eraseMagnifyingGlass() { // Called when the mouse moves + if (imageData != null) { + context.putImageData(imageData, + magnifyRectangle.x, + magnifyRectangle.y); + } +} + +function drawGlassIcon(context, radius) { + context.save(); + context.clearRect(0,0,context.canvas.width, + context.canvas.height); + + context.shadowColor = 'rgba(52, 72, 35, 0.5)'; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.shadowBlur = 2; + + context.beginPath(); + + context.translate(context.canvas.width/2, + context.canvas.height/2); + + context.beginPath(); + context.lineWidth = 1.5; + context.arc(0, 0, radius+3, 0, Math.PI*2, false); + context.strokeStyle = 'rgb(52, 72, 35)'; + context.stroke(); + + context.beginPath(); + context.lineWidth = 0.5; + context.strokeStyle = 'rgba(255,255,255,0.6)'; + context.arc(0, 0, radius+6, 0, Math.PI*2, false); + context.stroke(); + + context.restore(); +}; + +function drawMagnificationText(value, percent) { + scaleOutput.innerHTML = value; + percent = percent < 0.35 ? 0.35 : percent; + scaleOutput.style.fontSize = percent*MAXIMUM_SCALE/2 + 'em'; +} + +function updateMagnifyingGlass() { + eraseMagnifyingGlass(); + drawMagnifyingGlass({ x: magnifyingGlassX, y: magnifyingGlassY }); +} + +function step(time, lastTime, mouse, speed) { + var elapsedTime = time - lastTime, + nextLeft = mouse.x - magnifyingGlassRadius + speed.vx*(elapsedTime/10), + nextTop = mouse.y - magnifyingGlassRadius + speed.vy*(elapsedTime/10), + nextRight = nextLeft + magnifyingGlassRadius*2, + nextBottom = nextTop + magnifyingGlassRadius*2; + + eraseMagnifyingGlass(); + + if (nextLeft < 0) { + speed.vx = -speed.vx; + mouse.x = magnifyingGlassRadius; + } + else if (nextRight > canvas.width) { + speed.vx = -speed.vx; + mouse.x = canvas.width - magnifyingGlassRadius; + } + + if (nextTop < 0) { + speed.vy = -speed.vy; + mouse.y = magnifyingGlassRadius; + } + else if (nextBottom > canvas.height) { + speed.vy = -speed.vy; + mouse.y = canvas.height - magnifyingGlassRadius; + } + + mouse.x += speed.vx*(elapsedTime/10); + mouse.y += speed.vy*(elapsedTime/10); + + drawMagnifyingGlass(mouse); +} +function animate(mouse, speed) { + var time, lastTime = 0, elapsedTime; + animating = true; + + if (lastTime === 0) { + lastTime = +new Date; + } + + animationLoop = setInterval(function() { + var time = + new Date; + step(time, lastTime, mouse, speed); + lastTime = time; + }, 1000/60); +} + +function didThrow() { + var elapsedTime = mouseup.time - mousedown.time; + var elapsedMotion = Math.abs(mouseup.x - mousedown.x) + + Math.abs(mouseup.y - mousedown.y); + return (elapsedMotion / elapsedTime * 10) > 3; +} + +// Touch Event Handlers........................................ + +function isPinching (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + return changed === 1 || changed === 2 && touching === 2; +} + +function isDragging (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + return changed === 1 && touching === 1; +} + +canvas.ontouchstart = function (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + e.preventDefault(e); + + if (isDragging(e)) { + mouseDownOrTouchStart(windowToCanvas(e.pageX, e.pageY)); + } + else if (isPinching(e)) { + var touch1 = e.touches.item(0), + touch2 = e.touches.item(1), + point1 = windowToCanvas(touch1.pageX, touch1.pageY), + point2 = windowToCanvas(touch2.pageX, touch2.pageY); + + distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.x - point1.x, 2)); + pinchRatio = magnificationScale / distance; + } +}; + +canvas.ontouchmove = function (e) { + var changed = e.changedTouches.length, + touching = e.touches.length, + distance, touch1, touch2; + + e.preventDefault(e); + + if (isDragging(e)) { + mouseMoveOrTouchMove(windowToCanvas(e.pageX, e.pageY)); + } + else if (isPinching(e)) { + var touch1 = e.touches.item(0), + touch2 = e.touches.item(1), + point1 = windowToCanvas(touch1.pageX, touch1.pageY), + point2 = windowToCanvas(touch2.pageX, touch2.pageY), + scale; + + distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.x - point1.x, 2)); + scale = pinchRatio * distance; + + if (scale > 1 && scale < 3) { + magnificationScale = parseFloat(pinchRatio * distance).toFixed(2); + draw(); + } + } +}; + +canvas.ontouchend = function (e) { + e.preventDefault(e); + mouseUpOrTouchEnd(windowToCanvas(e.pageX, e.pageY)); +}; + +// Mouse Event Handlers........................................ + +canvas.onmousedown = function (e) { + e.preventDefault(e); + mouseDownOrTouchStart(windowToCanvas(e.clientX, e.clientY)); +}; + +canvas.onmousemove = function (e) { + e.preventDefault(e); + mouseMoveOrTouchMove(windowToCanvas(e.clientX, e.clientY)); +}; + +canvas.onmouseup = function (e) { + e.preventDefault(e); + mouseUpOrTouchEnd(windowToCanvas(e.clientX, e.clientY)); +}; + +function mouseDownOrTouchStart(mouse) { + mousedown = { x: mouse.x, y: mouse.y, time: (new Date).getTime() }; + + if (animating) { + animating = false; + clearInterval(animationLoop); + eraseMagnifyingGlass(); + } + else { + dragging = true; + context.save(); + } +}; + +function mouseMoveOrTouchMove(mouse) { + if (dragging) { + eraseMagnifyingGlass(); + drawMagnifyingGlass(mouse); + } +}; + +function mouseUpOrTouchEnd(mouse) { + mouseup = { x: mouse.x, y: mouse.y, time: (new Date).getTime() }; + + if (dragging) { + if (didThrow()) { + velocityX = (mouseup.x-mousedown.x)/100; + velocityY = (mouseup.y-mousedown.y)/100; + animate(mouse, { vx: velocityX, vy: velocityY }); + } + else { + //eraseMagnifyingGlass(); + } + } + dragging = false; +}; + +// Slider Event Handlers....................................... + +magnifyZoomSlider.addChangeListener( function(e) { + var maxRadius = (glassSizeCanvas.width/2-7); + percent = magnifyZoomSlider.knobPercent, + value = parseFloat(1 + percent * 2).toFixed(2); + + drawMagnificationText(value, percent); + magnificationScale = value; + updateMagnifyingGlass(); +}); + +glassSlider.addChangeListener( function(e) { + var maxRadius = glassSizeCanvas.width/2-5, + percent = parseFloat(glassSlider.knobPercent), + value = 25 + new Number((percent * 175).toFixed(0)); + + magnifyingGlassRadius = value + drawGlassIcon(glassSizeContext, maxRadius * percent); + updateMagnifyingGlass(); +}); + +// Initialization.............................................. + +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'rgba(250, 250, 0, 0.5)'; +context.shadowColor = 'rgba(0, 0, 0, 0.5)'; +context.shadowOffsetX = 10; +context.shadowOffsetY = 10; +context.shadowBlur = 20; + +function draw() { + var maxRadius = (glassSizeCanvas.width/2-7), + percent = parseFloat(glassSlider.knobPercent); + + context.drawImage(image, 0, 0, canvas.width, canvas.height); + drawGlassIcon(glassSizeContext, maxRadius * 0.5); + drawMagnificationText(magnificationScale, percent); + drawMagnifyingGlass({ x: magnifyingGlassX, y: magnifyingGlassY }); +} + +image.src = '../../shared/images/canyon.png'; +image.onload = function(e) { + draw(); +}; + +drawGlassIcon(glassSizeContext, (glassSizeCanvas.width/2-7)/2 ); + +canvas.addEventListener('dragenter', function (e) { + e.preventDefault(); + e.dataTransfer.effectAllowed = 'copy'; +}, false); + +canvas.addEventListener('dragover', function (e) { + e.preventDefault(); +}, false); + +window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + +canvas.addEventListener('drop', function (e) { + var file = e.dataTransfer.files[0]; + + window.requestFileSystem(window.TEMPORARY, 5*1024*1024, + function (fs) { + fs.root.getFile(file.name, {create: true}, + function (fileEntry) { + fileEntry.createWriter( function (writer) { + writer.write(file); + }); + image.src = fileEntry.toURL(); + }, + + function (e) { + alert(e.code); + } + ); + }, + + function (e) { + alert(e.code); + } + ); +}, false); + +magnifyZoomSlider.appendTo('magnificationSliderDiv'); +glassSlider.appendTo('glassSizeSliderDiv'); + +magnifyZoomSlider.draw(); +glassSlider.draw(); + +if (window.matchMedia && screen.width <= 1024) { + var m = window.matchMedia("(orientation:portrait)"), + lw = 0, lh = 0, lr = 0; + + function listener (mql) { + var cr = canvas.getBoundingClientRect(); + + if (mql.matches) { // portrait + // Save landscape size to reset later + lw = canvas.width; + lh = canvas.height; + lr = magnifyingGlassRadius; + + // Resize for portrait + canvas.width = screen.width - 2*cr.left; + canvas.height = canvas.width*canvasRatio; + + magnifyingGlassRadius *= (canvas.width + canvas.height) / (lw + lh); + } + else if (lw !== 0 && lh !== 0) { // landscape + // Reset landscape size + canvas.width = lw; + canvas.height = lh; + + magnifyingGlassRadius = lr; + } + + // Setting canvas width and height resets and + // erases the canvas, making a redraw necessary + + draw(); + } + + m.addListener( listener ); +} diff --git a/canvas/ch05/example-5.19/example.html b/canvas/ch05/example-5.19/example.html new file mode 100644 index 0000000..43899ee --- /dev/null +++ b/canvas/ch05/example-5.19/example.html @@ -0,0 +1,81 @@ + + + + + + A One Minute Stopwatch + + + + + + + Canvas not supported + + +
+ Seconds (0-60): + +
+ + + + + + diff --git a/canvas/ch05/example-5.19/example.js b/canvas/ch05/example-5.19/example.js new file mode 100644 index 0000000..05dc881 --- /dev/null +++ b/canvas/ch05/example-5.19/example.js @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + startStopButton = document.getElementById('startStopButton'), + secondsInput = document.getElementById('seconds'), + + CENTROID_RADIUS = 10, + CENTROID_STROKE_STYLE = 'rgba(0, 0, 0, 0.5)', + CENTROID_FILL_STYLE ='rgba(80, 190, 240, 0.6)', + + DEGREE_DIAL_MARGIN = 55, + TRACKING_DIAL_MARGIN = 80, + DEGREE_ANNOTATIONS_FILL_STYLE = 'rgba(0, 0, 230, 0.9)', + GUIDEWIRE_FILL_STYLE = 'rgba(85, 190, 240, 0.8)', + DEGREE_ANNOTATIONS_TEXT_SIZE = 18, + DEGREE_OUTER_DIAL_MARGIN = DEGREE_DIAL_MARGIN, + + TICK_WIDTH = 15, + TICK_LONG_STROKE_STYLE = 'rgba(100, 140, 230, 0.9)', + TICK_SHORT_STROKE_STYLE = 'rgba(100, 140, 230, 0.7)', + + TEXT_MARGIN = 135, + + TRACKING_DIAL_STROKING_STYLE = 'rgba(100, 140, 230, 0.5)', + + GUIDEWIRE_STROKE_STYLE = 'goldenrod', + GUIDEWIRE_FILL_STYLE = 'rgba(0, 0, 230, 0.9)', + circle = { x: canvas.width/2, + y: canvas.height/2, + radius: 150 + }, + + timerSetting = 10, + stopwatch = new Stopwatch(); + +// Functions..................................................... + +function windowToCanvas(canvas, x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +// Drawing functions............................................. + +function drawCentroid() { + context.beginPath(); + context.save(); + context.strokeStyle = CENTROID_STROKE_STYLE; + context.fillStyle = CENTROID_FILL_STYLE; + context.arc(circle.x, circle.y, CENTROID_RADIUS, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + context.restore(); +} + +function drawHand(loc) { + var initialAngle = -Math.PI/2 - (Math.PI / 180) * (timerSetting / 60 * 360), + angle = initialAngle, + stopwatchElapsed = stopwatch.getElapsedTime(), + seconds, + radius, + endpt; + + if (stopwatchElapsed) { + angle = -Math.PI/2 - (Math.PI / 180) * ((timerSetting - stopwatchElapsed/1000) / 60 * 360), + seconds = parseFloat(timerSetting - stopwatchElapsed/1000).toFixed(2); + if (seconds > 0) { + secondsInput.value = seconds; + } + } + + radius = circle.radius + TRACKING_DIAL_MARGIN; + + if (loc.x >= circle.x) { + endpt = { x: circle.x + radius * Math.cos(angle), + y: circle.y + radius * Math.sin(angle) + }; + } + else { + endpt = { x: circle.x - radius * Math.cos(angle), + y: circle.y - radius * Math.sin(angle) + }; + } + + context.save(); + + context.strokeStyle = GUIDEWIRE_STROKE_STYLE; + context.fillStyle = GUIDEWIRE_FILL_STYLE; + context.lineWidth = 1.5; + + context.beginPath(); + context.moveTo(circle.x, circle.y); + context.lineTo(endpt.x, endpt.y); + context.stroke(); + + context.beginPath(); + context.fillStyle = 'yellow'; + context.arc(endpt.x, endpt.y, 7, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + + context.restore(); +} + +function drawDegreeOuterDial() { + context.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + context.arc(circle.x, circle.y, + circle.radius + DEGREE_OUTER_DIAL_MARGIN, + 0, Math.PI*2, false); +} + +function drawDegreeAnnotations() { + var radius = circle.radius + TEXT_MARGIN; + + context.save(); + context.fillStyle = DEGREE_ANNOTATIONS_FILL_STYLE; + context.font = DEGREE_ANNOTATIONS_TEXT_SIZE + 'px Arial'; + + for (var angle=Math.PI/2, i=0; i < 60; angle += Math.PI/6, i+=5) { + context.beginPath(); + context.fillText(i, + circle.x + Math.cos(angle) * (radius - TICK_WIDTH*2), + circle.y - Math.sin(angle) * (radius - TICK_WIDTH*2)); + } + context.restore(); +} + +function drawDegreeDialTicks() { + var radius = circle.radius + DEGREE_DIAL_MARGIN, + ANGLE_MAX = 2*Math.PI, + ANGLE_DELTA = Math.PI/64; + + context.save(); + + for (var angle = 0, cnt = 0; angle < ANGLE_MAX; angle += ANGLE_DELTA, ++cnt) { + context.beginPath(); + + if (cnt % 4 === 0) { + context.moveTo(circle.x + Math.cos(angle) * (radius - TICK_WIDTH), + circle.y + Math.sin(angle) * (radius - TICK_WIDTH)); + context.lineTo(circle.x + Math.cos(angle) * (radius), + circle.y + Math.sin(angle) * (radius)); + context.strokeStyle = TICK_LONG_STROKE_STYLE; + context.stroke(); + } + else { + context.moveTo(circle.x + Math.cos(angle) * (radius - TICK_WIDTH/2), + circle.y + Math.sin(angle) * (radius - TICK_WIDTH/2)); + context.lineTo(circle.x + Math.cos(angle) * (radius), + circle.y + Math.sin(angle) * (radius)); + context.strokeStyle = TICK_SHORT_STROKE_STYLE; + context.stroke(); + } + + context.restore(); + } +} + +function drawDegreeTickDial() { + context.save(); + context.strokeStyle = 'rgba(0, 0, 0, 0.1)'; + context.arc(circle.x, circle.y, + circle.radius + DEGREE_DIAL_MARGIN - TICK_WIDTH, 0, Math.PI*2, false); + context.stroke(); + context.restore(); +} + +function drawTrackingDial() { + context.save(); + context.shadowColor = 'rgba(0, 0, 0, 0.7)'; + context.shadowOffsetX = 3, + context.shadowOffsetY = 3, + context.shadowBlur = 6, + context.strokeStyle = TRACKING_DIAL_STROKING_STYLE; + context.beginPath(); + context.arc(circle.x, circle.y, circle.radius + + TRACKING_DIAL_MARGIN, 0, Math.PI*2, true); + context.stroke(); + context.restore(); +} + +function drawDial() { + var loc = {x: circle.x, y: circle.y}; + + drawCentroid(); + drawHand(loc); + + drawTrackingDial(); + drawDegreeOuterDial(); + + context.fillStyle = 'rgba(218, 165, 35, 0.2)'; + context.fill(); + + context.beginPath(); + drawDegreeOuterDial(); + context.stroke(); + + drawDegreeTickDial(); + drawDegreeDialTicks(); + drawDegreeAnnotations(); +} + +function redraw() { + context.clearRect(0, 0, canvas.width, canvas.height); + drawGrid('lightgray', 10, 10); + drawDial(); +} + +function animate() { + if (stopwatch.isRunning() && + stopwatch.getElapsedTime() > timerSetting*1000) { // animation is over + stopwatch.stop(); + startStopButton.value = 'Start'; + secondsInput.disabled = false; + secondsInput.value = 0; + } + else if (stopwatch.isRunning()) { // animation is running + redraw(); + requestNextAnimationFrame(animate); + } +} + +startStopButton.onclick = function (e) { + var value = startStopButton.value; + if (value === 'Start') { + stopwatch.start(); + startStopButton.value = 'Stop'; + requestNextAnimationFrame(animate); + secondsInput.disabled = true; + } + else { + stopwatch.stop(); + timerSetting = parseFloat(secondsInput.value); + startStopButton.value = 'Start'; + secondsInput.disabled = false; + } + stopwatch.reset(); +}; + +secondsInput.onchange = function (e) { + timerSetting = parseFloat(secondsInput.value); + redraw(); +}; + +// Initialization................................................ + +drawGrid('lightgray', 10, 10); + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(0, 0, 0, 0.4)'; + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; + +context.textAlign = 'center'; +context.textBaseline = 'middle'; + +drawDial(); diff --git a/canvas/ch05/example-5.9/example.html b/canvas/ch05/example-5.9/example.html new file mode 100644 index 0000000..6349fe5 --- /dev/null +++ b/canvas/ch05/example-5.9/example.html @@ -0,0 +1,70 @@ + + + + + Using requestAnimationFrame() + + + + + +
+ +
+ + + Canvas not supported + + + + + + diff --git a/canvas/ch05/example-5.9/example.js b/canvas/ch05/example-5.9/example.js new file mode 100644 index 0000000..78c199d --- /dev/null +++ b/canvas/ch05/example-5.9/example.js @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + paused = true, + discs = [ + { + x: 150, + y: 250, + lastX: 150, + lastY: 250, + velocityX: -3.2, + velocityY: 3.5, + radius: 25, + innerColor: 'rgba(255,255,0,1)', + middleColor: 'rgba(255,255,0,0.7)', + outerColor: 'rgba(255,255,0,0.5)', + strokeStyle: 'gray', + }, + + { + x: 50, + y: 150, + lastX: 50, + lastY: 150, + velocityX: 2.2, + velocityY: 2.5, + radius: 25, + innerColor: 'rgba(100,145,230,1.0)', + middleColor: 'rgba(100,145,230,0.7)', + outerColor: 'rgba(100,145,230,0.5)', + strokeStyle: 'blue' + }, + + { + x: 150, + y: 75, + lastX: 150, + lastY: 75, + velocityX: 1.2, + velocityY: 1.5, + radius: 25, + innerColor: 'rgba(255,0,0,1.0)', + middleColor: 'rgba(255,0,0,0.7)', + outerColor: 'rgba(255,0,0,0.5)', + strokeStyle: 'orange' + }, + ], + numDiscs = discs.length, + animateButton = document.getElementById('animateButton'); + +// Functions..................................................... + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + context.strokeStyle = 'lightgray'; + context.lineWidth = 0.5; + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } + + context.save(); + + context.strokeStyle = 'rgba(100,0,0,0.3)'; + context.lineWidth = 1; + + context.beginPath(); + + context.moveTo(35,0); + context.lineTo(35,context.canvas.height); + context.stroke(); + + context.restore(); +} + +function update() { + var disc = null; + + for(var i=0; i < numDiscs; ++i) { + disc = discs[i]; + + if (disc.x + disc.velocityX + disc.radius > context.canvas.width || + disc.x + disc.velocityX - disc.radius < 0) + disc.velocityX = -disc.velocityX; + + if (disc.y + disc.velocityY + disc.radius > context.canvas.height || + disc.y + disc.velocityY - disc.radius < 0) + disc.velocityY= -disc.velocityY; + + disc.x += disc.velocityX; + disc.y += disc.velocityY; + } +} + +function draw() { + var disc = discs[i]; + + for(var i=0; i < numDiscs; ++i) { + disc = discs[i]; + + gradient = context.createRadialGradient(disc.x, disc.y, 0, + disc.x, disc.y, disc.radius); + + gradient.addColorStop(0.3, disc.innerColor); + gradient.addColorStop(0.5, disc.middleColor); + gradient.addColorStop(1.0, disc.outerColor); + + context.save(); + context.beginPath(); + context.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false); + context.fillStyle = gradient; + context.strokeStyle = disc.strokeStyle; + context.fill(); + context.stroke(); + context.restore(); + } +} + +// Animation..................................................... + +function animate(time) { + if (!paused) { + context.clearRect(0,0,canvas.width,canvas.height); + drawBackground(); + update(); + draw(); + + window.requestNextAnimationFrame(animate); + } +} + +// Initialization................................................ + +context.font = '48px Helvetica'; + +animateButton.onclick = function (e) { + paused = paused ? false : true; + if (paused) { + animateButton.value = 'Animate'; + } + else { + window.requestNextAnimationFrame(animate); + animateButton.value = 'Pause'; + } +}; diff --git a/canvas/ch06/example-6.1/example.html b/canvas/ch06/example-6.1/example.html new file mode 100644 index 0000000..f393ae8 --- /dev/null +++ b/canvas/ch06/example-6.1/example.html @@ -0,0 +1,58 @@ + + + + + + A simple sprite + + + + + + + Canvas not supported + + + + + + diff --git a/canvas/ch06/example-6.1/example.js b/canvas/ch06/example-6.1/example.js new file mode 100644 index 0000000..474c86b --- /dev/null +++ b/canvas/ch06/example-6.1/example.js @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'), + RADIUS = 75, + ball = new Sprite('ball', + { + paint: function(sprite, context) { + context.beginPath(); + context.arc(sprite.left + sprite.width/2, + sprite.top + sprite.height/2, + RADIUS, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgb(0,0,0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.lineWidth = 2; + context.strokeStyle = 'rgb(100,100,195)'; + context.fillStyle = 'rgba(30,144,255,0.15)'; + context.fill(); + context.stroke(); + } + } + ); + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +drawGrid('lightgray', 10, 10); + +ball.left = 320; +ball.top = 160; +ball.paint(context); diff --git a/canvas/ch06/example-6.10/bomb-no-fuse.png b/canvas/ch06/example-6.10/bomb-no-fuse.png new file mode 100644 index 0000000..f2e8cb2 Binary files /dev/null and b/canvas/ch06/example-6.10/bomb-no-fuse.png differ diff --git a/canvas/ch06/example-6.10/example.html b/canvas/ch06/example-6.10/example.html new file mode 100644 index 0000000..77834d9 --- /dev/null +++ b/canvas/ch06/example-6.10/example.html @@ -0,0 +1,75 @@ + + + + + Sprite Animators + + + + + + + Canvas not supported + + + + + + + + + + + diff --git a/canvas/ch06/example-6.10/example.js b/canvas/ch06/example-6.10/example.js new file mode 100644 index 0000000..67bea1d --- /dev/null +++ b/canvas/ch06/example-6.10/example.js @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + explosionButton = document.getElementById('explosionButton'), + + BOMB_LEFT = 100, + BOMB_TOP = 80, + BOMB_WIDTH = 180, + BOMB_HEIGHT = 130, + + NUM_EXPLOSION_PAINTERS = 9, + NUM_FUSE_PAINTERS = 9, + + // Painters.................................................. + + bombPainter = new ImagePainter('../../shared/images/bomb.png'), + bombNoFusePainter = new ImagePainter('bomb-no-fuse.png'), + fuseBurningPainters = [], + explosionPainters = [], + + // Animators................................................. + + fuseBurningAnimator = new SpriteAnimator( + fuseBurningPainters, + function () { bomb.painter = bombNoFusePainter; }); + + explosionAnimator = new SpriteAnimator( + explosionPainters, + function () { bomb.painter = bombNoFusePainter; }); + + // Bomb...................................................... + + bomb = new Sprite('bomb', bombPainter), + +// Event Handlers................................................ + +explosionButton.onclick = function (e) { + if (bomb.animating) // not now... + return; + + // burn fuse for 2 seconds + + fuseBurningAnimator.start(bomb, 2000); + + // wait for 3 seconds, then explode for 1 second + + setTimeout(function () { + explosionAnimator.start(bomb, 1000); + + // wait for 2 seconds, then reset to the + // original bomb image + + setTimeout(function () { + bomb.painter = bombPainter; + }, 2000); + + }, 3000); + +}; + +// Animation..................................................... + +function animate(now) { + context.clearRect(0,0,canvas.width,canvas.height); + bomb.paint(context); + window.requestNextAnimationFrame(animate); +} + +// Initialization................................................ + +bomb.left = BOMB_LEFT; +bomb.top = BOMB_TOP; +bomb.width = BOMB_WIDTH; +bomb.height = BOMB_HEIGHT; + +for (var i=0; i < NUM_FUSE_PAINTERS; ++i) { + fuseBurningPainters.push(new ImagePainter('fuse-0' + i + '.png')); +} + +for (var i=0; i < NUM_EXPLOSION_PAINTERS; ++i) { + explosionPainters.push(new ImagePainter('explosion-0' + i + '.png')); +} + +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch06/example-6.10/explosion-00.png b/canvas/ch06/example-6.10/explosion-00.png new file mode 100644 index 0000000..228ade3 Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-00.png differ diff --git a/canvas/ch06/example-6.10/explosion-01.png b/canvas/ch06/example-6.10/explosion-01.png new file mode 100644 index 0000000..5fdc2fa Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-01.png differ diff --git a/canvas/ch06/example-6.10/explosion-02.png b/canvas/ch06/example-6.10/explosion-02.png new file mode 100644 index 0000000..84b6993 Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-02.png differ diff --git a/canvas/ch06/example-6.10/explosion-03.png b/canvas/ch06/example-6.10/explosion-03.png new file mode 100644 index 0000000..b8a8327 Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-03.png differ diff --git a/canvas/ch06/example-6.10/explosion-04.png b/canvas/ch06/example-6.10/explosion-04.png new file mode 100644 index 0000000..a6f223b Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-04.png differ diff --git a/canvas/ch06/example-6.10/explosion-05.png b/canvas/ch06/example-6.10/explosion-05.png new file mode 100644 index 0000000..35ddde3 Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-05.png differ diff --git a/canvas/ch06/example-6.10/explosion-06.png b/canvas/ch06/example-6.10/explosion-06.png new file mode 100644 index 0000000..0705fb2 Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-06.png differ diff --git a/canvas/ch06/example-6.10/explosion-07.png b/canvas/ch06/example-6.10/explosion-07.png new file mode 100644 index 0000000..c7c20d2 Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-07.png differ diff --git a/canvas/ch06/example-6.10/explosion-08.png b/canvas/ch06/example-6.10/explosion-08.png new file mode 100644 index 0000000..05935bd Binary files /dev/null and b/canvas/ch06/example-6.10/explosion-08.png differ diff --git a/canvas/ch06/example-6.10/fuse-00.png b/canvas/ch06/example-6.10/fuse-00.png new file mode 100644 index 0000000..65009f0 Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-00.png differ diff --git a/canvas/ch06/example-6.10/fuse-01.png b/canvas/ch06/example-6.10/fuse-01.png new file mode 100644 index 0000000..08c16e3 Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-01.png differ diff --git a/canvas/ch06/example-6.10/fuse-02.png b/canvas/ch06/example-6.10/fuse-02.png new file mode 100644 index 0000000..1ec150b Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-02.png differ diff --git a/canvas/ch06/example-6.10/fuse-03.png b/canvas/ch06/example-6.10/fuse-03.png new file mode 100644 index 0000000..f71331a Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-03.png differ diff --git a/canvas/ch06/example-6.10/fuse-04.png b/canvas/ch06/example-6.10/fuse-04.png new file mode 100644 index 0000000..451a826 Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-04.png differ diff --git a/canvas/ch06/example-6.10/fuse-05.png b/canvas/ch06/example-6.10/fuse-05.png new file mode 100644 index 0000000..a59d43a Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-05.png differ diff --git a/canvas/ch06/example-6.10/fuse-06.png b/canvas/ch06/example-6.10/fuse-06.png new file mode 100644 index 0000000..15642f8 Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-06.png differ diff --git a/canvas/ch06/example-6.10/fuse-07.png b/canvas/ch06/example-6.10/fuse-07.png new file mode 100644 index 0000000..dd5b85c Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-07.png differ diff --git a/canvas/ch06/example-6.10/fuse-08.png b/canvas/ch06/example-6.10/fuse-08.png new file mode 100644 index 0000000..ceec13b Binary files /dev/null and b/canvas/ch06/example-6.10/fuse-08.png differ diff --git a/canvas/ch06/example-6.2/example.html b/canvas/ch06/example-6.2/example.html new file mode 100644 index 0000000..ac264f7 --- /dev/null +++ b/canvas/ch06/example-6.2/example.html @@ -0,0 +1,61 @@ + + + + + + Sprite Clock + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch06/example-6.2/example.js b/canvas/ch06/example-6.2/example.js new file mode 100644 index 0000000..7b3a753 --- /dev/null +++ b/canvas/ch06/example-6.2/example.js @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + CLOCK_RADIUS = canvas.width/2 - 15, + HOUR_HAND_TRUNCATION = 35, + + // Painter................................................... + + ballPainter = { + paint: function (sprite, context) { + var x = sprite.left + sprite.width/2, + y = sprite.top + sprite.height/2, + width = sprite.width, + height = sprite.height, + radius = sprite.width/2; + + context.save(); + context.beginPath(); + context.arc(x, y, radius, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgb(0,0,0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.fillStyle = 'rgba(218, 165, 32, 0.1)'; + context.fill(); + + context.lineWidth = 2; + context.strokeStyle = 'rgb(100,100,195)'; + context.stroke(); + + context.restore(); + } + }, + + // Sprite.................................................... + + ball = new Sprite('ball', ballPainter); + +// Functions..................................................... + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +function drawHand(loc, isHour) { + var angle = (Math.PI*2) * (loc/60) - Math.PI/2, + handRadius = isHour ? CLOCK_RADIUS - HOUR_HAND_TRUNCATION + : CLOCK_RADIUS, + lineEnd = { + x: canvas.width/2 + + Math.cos(angle)*(handRadius - ball.width/2), + + y: canvas.height/2 + + Math.sin(angle)*(handRadius - ball.width/2) + }; + + context.beginPath(); + context.moveTo(canvas.width/2, canvas.height/2); + context.lineTo(lineEnd.x, lineEnd.y); + context.stroke(); + + ball.left = canvas.width/2 + + Math.cos(angle)*handRadius - ball.width/2; + + ball.top = canvas.height/2 + + Math.sin(angle)*handRadius - ball.height/2; + + ball.paint(context); +} + +function drawClock() { + drawClockFace(); + drawHands(); +} + +function drawHands() { + var date = new Date(), + hour = date.getHours(); + + ball.width = 20; + ball.height = 20; + drawHand(date.getSeconds(), false); + + hour = hour > 12 ? hour - 12 : hour; + ball.width = 35; + ball.height = 35; + drawHand(date.getMinutes(), false); + + ball.width = 50; + ball.height = 50; + drawHand(hour*5 + (date.getMinutes()/60)*5); + + ball.width = 10; + ball.height = 10; + ball.left = canvas.width/2 - ball.width/2; + ball.top = canvas.height/2 - ball.height/2; + ballPainter.paint(ball, context); +} + +function drawClockFace() { + context.beginPath(); + context.arc(canvas.width/2, canvas.height/2, + CLOCK_RADIUS, 0, Math.PI*2, false); + + context.save(); + context.strokeStyle = 'rgba(0,0,0,0.2)'; + context.stroke(); + context.restore(); +} + +// Animation..................................................... + +function animate() { + context.clearRect(0,0,canvas.width,canvas.height); + + drawGrid('lightgray', 10, 10); + drawClock(); + + window.requestNextAnimationFrame(animate); +} + +// Initialization................................................ + +context.lineWidth = 0.5; +context.strokeStyle = 'rgba(0,0,0,0.2)'; + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(0,0,0,0.5)'; + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; +context.stroke(); + +window.requestNextAnimationFrame(animate); + +drawGrid('lightgray', 10, 10); diff --git a/canvas/ch06/example-6.5/example.html b/canvas/ch06/example-6.5/example.html new file mode 100644 index 0000000..714d35a --- /dev/null +++ b/canvas/ch06/example-6.5/example.html @@ -0,0 +1,64 @@ + + + + + Image Painters + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch06/example-6.5/example.js b/canvas/ch06/example-6.5/example.js new file mode 100644 index 0000000..bf35b3c --- /dev/null +++ b/canvas/ch06/example-6.5/example.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + bomb = new Sprite('bomb', new ImagePainter('../../shared/images/bomb.png')), + + BOMB_LEFT = 220, + BOMB_TOP = 80, + BOMB_WIDTH = 180, + BOMB_HEIGHT = 130; + +function paint() { + bomb.paint(context); +} + +function animate(now) { + context.clearRect(0,0,canvas.width,canvas.height); + paint(); + window.requestNextAnimationFrame(animate); +} + +bomb.left = BOMB_LEFT; +bomb.top = BOMB_TOP; +bomb.width = BOMB_WIDTH; +bomb.height = BOMB_HEIGHT; + +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch06/example-6.7/example.html b/canvas/ch06/example-6.7/example.html new file mode 100644 index 0000000..7dcdc7a --- /dev/null +++ b/canvas/ch06/example-6.7/example.html @@ -0,0 +1,70 @@ + + + + + + Animating with sprite sheets + + + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch06/example-6.7/example.js b/canvas/ch06/example-6.7/example.js new file mode 100644 index 0000000..79df489 --- /dev/null +++ b/canvas/ch06/example-6.7/example.js @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + animateButton = document.getElementById('animateButton'), + spritesheet = new Image(), + runnerCells = [ + { left: 0, top: 0, width: 47, height: 64 }, + { left: 55, top: 0, width: 44, height: 64 }, + { left: 107, top: 0, width: 39, height: 64 }, + { left: 150, top: 0, width: 46, height: 64 }, + { left: 208, top: 0, width: 49, height: 64 }, + { left: 265, top: 0, width: 46, height: 64 }, + { left: 320, top: 0, width: 42, height: 64 }, + { left: 380, top: 0, width: 35, height: 64 }, + { left: 425, top: 0, width: 35, height: 64 }, + ], + sprite = new Sprite('runner', new SpriteSheetPainter(runnerCells)), + interval, + lastAdvance = 0, + paused = false, + PAGEFLIP_INTERVAL = 100; + +// Functions..................................................... + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + while(i > STEP_Y*4) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + i -= STEP_Y; + } +} + +function pauseAnimation() { + animateButton.value = 'Animate'; + paused = true; +} + +function startAnimation() { + animateButton.value = 'Pause'; + paused = false; + lastAdvance = 0; + window.requestNextAnimationFrame(animate); +} + +// Event handlers................................................ + +animateButton.onclick = function (e) { + if (animateButton.value === 'Animate') startAnimation(); + else pauseAnimation(); +}; + +// Animation..................................................... + +function animate(time) { + if ( ! paused) { + context.clearRect(0,0,canvas.width,canvas.height); + drawBackground(); + context.drawImage(spritesheet, 0, 0); + + sprite.paint(context); + + if (time - lastAdvance > PAGEFLIP_INTERVAL) { + sprite.painter.advance(); + lastAdvance = time; + } + window.requestNextAnimationFrame(animate); + } +} + +// Initialization................................................ + +spritesheet.src = '../../shared/images/running-sprite-sheet.png'; +spritesheet.onload = function(e) { + context.drawImage(spritesheet, 0, 0); +}; + +sprite.left = 200; +sprite.top = 100; + +context.strokeStyle = 'lightgray'; +context.lineWidth = 0.5; + +drawBackground(); diff --git a/canvas/ch06/example-6.9/example.html b/canvas/ch06/example-6.9/example.html new file mode 100644 index 0000000..61bb0fe --- /dev/null +++ b/canvas/ch06/example-6.9/example.html @@ -0,0 +1,62 @@ + + + + + + Combining sprite behaviors + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch06/example-6.9/example.js b/canvas/ch06/example-6.9/example.js new file mode 100644 index 0000000..80acef0 --- /dev/null +++ b/canvas/ch06/example-6.9/example.js @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + spritesheet = new Image(), + runnerCells = [ + { left: 0, top: 0, width: 47, height: 64 }, + { left: 55, top: 0, width: 44, height: 64 }, + { left: 107, top: 0, width: 39, height: 64 }, + { left: 152, top: 0, width: 46, height: 64 }, + { left: 208, top: 0, width: 49, height: 64 }, + { left: 265, top: 0, width: 46, height: 64 }, + { left: 320, top: 0, width: 42, height: 64 }, + { left: 380, top: 0, width: 35, height: 64 }, + { left: 425, top: 0, width: 35, height: 64 }, + ]; + + // Behaviors................................................. + + runInPlace = { + lastAdvance: 0, + PAGEFLIP_INTERVAL: 100, + + execute: function (sprite, context, time) { + if (time - this.lastAdvance > this.PAGEFLIP_INTERVAL) { + sprite.painter.advance(); + this.lastAdvance = time; + } + } + }, + + moveLeftToRight = { + lastMove: 0, + + execute: function (sprite, context, time) { + if (this.lastMove !== 0) { + sprite.left -= sprite.velocityX * + ((time - this.lastMove) / 1000); + + if (sprite.left < 0) { + sprite.left = canvas.width; + } + } + this.lastMove = time; + } + }, + + // Sprite.................................................... + + sprite = new Sprite('runner', + new SpriteSheetPainter(runnerCells), + [ runInPlace, moveLeftToRight ]); + +// Functions..................................................... + +function drawBackground() { + var STEP_Y = 12, + i = context.canvas.height; + + while(i > STEP_Y*4) { + context.beginPath(); + + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + + i -= STEP_Y; + } +} + +// Animation..................................................... + +function animate(time) { + context.clearRect(0,0,canvas.width,canvas.height); + drawBackground(); + + context.drawImage(spritesheet, 0, 0); + + sprite.update(context, time); + sprite.paint(context); + + window.requestNextAnimationFrame(animate); +} + +// Initialization................................................ + +spritesheet.src = '../../shared/images/running-sprite-sheet.png'; + +spritesheet.onload = function(e) { + context.drawImage(spritesheet, 0, 0); +}; + +sprite.velocityX = 50; // pixels/second +sprite.left = 200; +sprite.top = 100; + +context.strokeStyle = 'lightgray'; +context.lineWidth = 0.5; + +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch06/section-6.3.2/example.html b/canvas/ch06/section-6.3.2/example.html new file mode 100644 index 0000000..3b64a2c --- /dev/null +++ b/canvas/ch06/section-6.3.2/example.html @@ -0,0 +1,83 @@ + + + + + Timed Behaviors + + + + + + + Canvas not supported + + +
+ + canvas not supported + +
+ + + + + + + + diff --git a/canvas/ch06/section-6.3.2/example.js b/canvas/ch06/section-6.3.2/example.js new file mode 100644 index 0000000..22211fe --- /dev/null +++ b/canvas/ch06/section-6.3.2/example.js @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + thrustersCanvas = document.getElementById('thrustersCanvas'), + thrustersContext = thrustersCanvas.getContext('2d'), + + RIGHT = 1, + LEFT = 2, + ARROW_MARGIN = 10, + BALL_RADIUS = 15, + LEDGE_LEFT = 150, + LEDGE_TOP = 90, + LEDGE_WIDTH = 44, + LEDGE_HEIGHT = 12, + ANIMATION_DURATION = 200, + + THRUSTER_FILL_STYLE = 'rgba(100,140,230,0.8)', + THRUSTER_FIRING_FILL_STYLE = 'rgba(255,255,0,0.8)', + + lastTime = 0, + arrow = LEFT, + + // Push animation............................................ + + pushTimer = new AnimationTimer(ANIMATION_DURATION), + + // Move ball behavior........................................ + + moveBall = { + lastTime: undefined, + + resetBall: function () { + ball.left = LEDGE_LEFT + LEDGE_WIDTH/2 - BALL_RADIUS; + ball.top = LEDGE_TOP - BALL_RADIUS*2; + }, + + execute: function (sprite, context, time) { + var timerElapsed = pushTimer.getElapsedTime(), + frameElapsed; + + if (pushTimer.isRunning() && this.lastTime !== undefined) { + frameElapsed = timerElapsed - this.lastTime; + + if (arrow === LEFT) ball.left -= ball.velocityX * (frameElapsed/1000); + else ball.left += ball.velocityX * (frameElapsed/1000); + + if ((isBallOnLedge() && pushTimer.isOver()) || ! isBallOnLedge()) + pushTimer.stop(); + + if ( ! isBallOnLedge()) + this.resetBall(); + } + this.lastTime = timerElapsed; + } + }, + + // Ball sprite............................................... + + ball = new Sprite('ball', + { + paint: function (sprite, context) { + context.save(); + context.beginPath(); + context.arc(sprite.left + sprite.width/2, sprite.top + sprite.height/2, + BALL_RADIUS, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgb(0,0,255)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.lineWidth = 2; + context.strokeStyle = 'rgb(100,100,195)'; + context.stroke(); + + context.beginPath(); + context.arc(sprite.left + sprite.width/2, sprite.top + sprite.height/2, + BALL_RADIUS/2, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgb(255,255,0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + context.stroke(); + + context.restore(); + } + }, + + [ moveBall ] + ), + + ledge = new Sprite('ledge', + { + paint: function (sprite, context) { + context.save(); + context.shadowColor = 'rgba(0,0,0,0.8)'; + context.shadowBlur = 8; + context.shadowOffsetX = 4; + context.shadowOffsetY = 4; + + context.fillStyle = 'rgba(255,255,0,0.6)'; + context.fillRect(sprite.left,sprite.top, + sprite.width,sprite.height); + context.restore(); + } + } + ); + +// Behavior functions............................................ + +function restartAnimation() { + if (pushTimer.isRunning()) { + pushTimer.stop(); + } + pushTimer.start(); +} + +function pushBallLeft() { + arrow = LEFT; + restartAnimation(); +} + +function pushBallRight() { + arrow = RIGHT; + restartAnimation(); +} + +function isBallOnLedge() { + return ball.left + 2*BALL_RADIUS > LEDGE_LEFT && + ball.left < LEDGE_LEFT + LEDGE_WIDTH; +} + +// Paint functions............................................... + +function paintThrusters() { + thrustersContext.clearRect(0,0, + thrustersCanvas.width,thrustersCanvas.height); + + if (arrow === LEFT) { + thrustersContext.fillStyle = + pushTimer.isRunning() ? THRUSTER_FIRING_FILL_STYLE : + THRUSTER_FILL_STYLE; + paintLeftArrow(thrustersContext); + thrustersContext.fillStyle = THRUSTER_FILL_STYLE; + paintRightArrow(thrustersContext); + } + else { + thrustersContext.fillStyle = + pushTimer.isRunning() ? THRUSTER_FIRING_FILL_STYLE : + THRUSTER_FILL_STYLE; + paintRightArrow(thrustersContext); + thrustersContext.fillStyle = THRUSTER_FILL_STYLE; + paintLeftArrow(thrustersContext); + } +} + +function paintRightArrow(context) { + thrustersContext.save(); + thrustersContext.translate(thrustersCanvas.width, 0); + thrustersContext.scale(-1,1); + paintArrow(context); + thrustersContext.restore(); +} + +function paintLeftArrow(context) { + paintArrow(context); +} + +function paintArrow(context) { + context.beginPath(); + + context.moveTo( thrustersCanvas.width/2 - ARROW_MARGIN/2, + ARROW_MARGIN/2); + + context.lineTo( thrustersCanvas.width/2 - ARROW_MARGIN/2, + thrustersCanvas.height - ARROW_MARGIN); + + context.quadraticCurveTo(thrustersCanvas.width/2 - ARROW_MARGIN/2, + thrustersCanvas.height - ARROW_MARGIN/2, + thrustersCanvas.width/2 - ARROW_MARGIN, + thrustersCanvas.height - ARROW_MARGIN/2); + + context.lineTo( ARROW_MARGIN, + thrustersCanvas.height/2 + ARROW_MARGIN/2); + + context.quadraticCurveTo(ARROW_MARGIN - 3, + thrustersCanvas.height/2, + ARROW_MARGIN, thrustersCanvas.height/2 - ARROW_MARGIN/2); + + context.lineTo( thrustersCanvas.width/2 - ARROW_MARGIN, + ARROW_MARGIN/2); + + context.quadraticCurveTo(thrustersCanvas.width/2 - ARROW_MARGIN, + ARROW_MARGIN/2, thrustersCanvas.width/2 - ARROW_MARGIN/2, + ARROW_MARGIN/2); + context.fill(); + context.stroke(); +} + +// Event handlers................................................ + +thrustersCanvas.onmousedown = function canvasMouseDown(e) { + var rect = thrustersCanvas.getBoundingClientRect(), + x = e.x || e.clientX, + y = e.y || e.clientY; + + e.preventDefault(); + e.stopPropagation(); + + if (x-rect.left > thrustersCanvas.width/2) { + pushBallRight(); + } + else { + pushBallLeft(); + } +}; + +// Animation functions........................................... + +function calculateFps(time) { + var fps = 1000 / (time - lastTime); + lastTime = time; + return fps; +} + +function animate(time) { + fps = calculateFps(time); + + context.clearRect(0,0,canvas.width,canvas.height); + + ball.update(context, time); + ball.paint(context); + + ledge.update(context, time); + ledge.paint(context); + + paintThrusters(); + + window.requestNextAnimationFrame(animate); +} + +// Initialization................................................ + +thrustersContext.strokeStyle = 'rgba(100,140,230,0.6)'; +thrustersContext.shadowColor = 'rgba(0,0,0,0.3)'; +thrustersContext.shadowBlur = 6; +thrustersContext.shadowX = 4; +thrustersContext.shadowY = 4; + +window.requestNextAnimationFrame(animate); + +ball.left = LEDGE_LEFT + LEDGE_WIDTH/2 - BALL_RADIUS; +ball.top = LEDGE_TOP - BALL_RADIUS*2; +ball.width = BALL_RADIUS*2; +ball.height = BALL_RADIUS*2; +ball.velocityX = 110; +ball.velocityY = 0; + +ledge.left = LEDGE_LEFT; +ledge.top = LEDGE_TOP; +ledge.width = LEDGE_WIDTH; diff --git a/canvas/ch07/example-7.1/example.html b/canvas/ch07/example-7.1/example.html new file mode 100644 index 0000000..e1775f3 --- /dev/null +++ b/canvas/ch07/example-7.1/example.html @@ -0,0 +1,85 @@ + + + + + Falling + + + + + + + Canvas not supported + + + + canvas not supported + + + + + + + + + diff --git a/canvas/ch07/example-7.1/example.js b/canvas/ch07/example-7.1/example.js new file mode 100644 index 0000000..40f14f7 --- /dev/null +++ b/canvas/ch07/example-7.1/example.js @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.querySelector('#canvas'), + context = canvas.getContext('2d'), + + thrusterCanvas = document.querySelector('#thrusterCanvas'), + thrusterContext = thrusterCanvas.getContext('2d'), + + RIGHT = 1, + LEFT = 2, + ARROW_MARGIN = 10, + BALL_RADIUS = 23, + LEDGE_LEFT = 280, + LEDGE_TOP = 55, + LEDGE_WIDTH = 50, + LEDGE_HEIGHT = 12, + + GRAVITY_FORCE = 9.81, // 9.81 m/s / s + + lastTime = 0, + fps = 60, + arrow = LEFT, + + PLATFORM_HEIGHT_IN_METERS = 10, // 10 meters + pixelsPerMeter = (canvas.height - LEDGE_TOP) / + PLATFORM_HEIGHT_IN_METERS, + + moveBall = { + lastFrameTime: undefined, + + execute: function (sprite, context, time) { + var now = +new Date(); + if (this.lastFrameTime == undefined) { + this.lastFrameTime = now; + return; + } + + if (pushAnimationTimer.isRunning()) { + if (arrow === LEFT) sprite.left -= sprite.velocityX / fps; + else sprite.left += sprite.velocityX / fps; + + if (isBallOnLedge()) { + if (pushAnimationTimer.getElapsedTime() > 200) { + pushAnimationTimer.stop(); + } + } + else if ( ! fallingAnimationTimer.isRunning()) { + startFalling(); + this.lastFrameTime = now; + } + } + + if (fallingAnimationTimer.isRunning()) { + sprite.top += sprite.velocityY / fps; + sprite.velocityY = GRAVITY_FORCE * + (fallingAnimationTimer.getElapsedTime()/1000) * pixelsPerMeter; + + if (sprite.top > canvas.height) { + stopFalling(); + } + } + } + }, + + pushAnimationTimer = new AnimationTimer(), + fallingAnimationTimer = new AnimationTimer(), + + ball = new Sprite('ball', + { + paint: function (sprite, context) { + context.save(); + context.beginPath(); + context.arc(sprite.left + sprite.width/2, sprite.top + sprite.height/2, + BALL_RADIUS, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgba(0,0,255,0.7)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.lineWidth = 2; + context.strokeStyle = 'rgba(100,100,195,0.8)'; + context.stroke(); + + context.beginPath(); + context.arc(sprite.left + sprite.width/2, sprite.top + sprite.height/2, + BALL_RADIUS/2, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgba(255,255,0,1.0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + context.stroke(); + + context.restore(); + } + }, + + [ moveBall ] + ), + + ledge = new Sprite('ledge', + { + paint: function (sprite, context) { + context.save(); + context.shadowColor = 'rgba(0,0,0,0.5)'; + context.shadowBlur = 8; + context.shadowOffsetX = 2; + context.shadowOffsetY = 2; + + context.fillStyle = 'rgba(255,255,0,0.6)'; + context.strokeStyle = 'rgba(0,0,0,0.6)'; + context.beginPath(); + context.rect(sprite.left,sprite.top,sprite.width,sprite.height); + context.fill(); + context.stroke(); + context.restore(); + } + } + ); + +// Behavior functions............................................ + +function pushBallLeft() { + if (pushAnimationTimer.isRunning()) { + pushAnimationTimer.stop(); + } + arrow = LEFT; + pushAnimationTimer.start(); +} + +function pushBallRight() { + if (pushAnimationTimer.isRunning()) { + pushAnimationTimer.stop(); + } + arrow = RIGHT; + pushAnimationTimer.start(); +} + +function startFalling() { + fallingAnimationTimer.start(); + ball.velocityY = 0; +} + +function stopFalling() { + fallingAnimationTimer.stop(); + pushAnimationTimer.stop(); + + ball.left = LEDGE_LEFT + LEDGE_WIDTH/2 - BALL_RADIUS; + ball.top = LEDGE_TOP - BALL_RADIUS*2; + + ball.velocityY = 0; +} + +function isBallOnLedge() { + return ball.left + BALL_RADIUS > LEDGE_LEFT && + ball.left < LEDGE_LEFT + LEDGE_WIDTH; +} + +// Paint functions............................................... + +function paintThruster() { + thrusterContext.clearRect(0,0, + thrusterCanvas.width,thrusterCanvas.height); + if (pushAnimationTimer.isRunning()) + thrusterContext.fillStyle = 'rgba(255,255,0,0.5)'; + else + thrusterContext.fillStyle = 'rgba(100,140,255,0.5)'; + + paintArrow(thrusterContext); +} + +function paintLeftArrow(context) { + paintArrow(context); +} + +function paintArrow(context) { + context.save(); + context.beginPath(); + + context.moveTo( thrusterCanvas.width - ARROW_MARGIN/2, + ARROW_MARGIN/2); + + context.lineTo( thrusterCanvas.width - ARROW_MARGIN/2, + thrusterCanvas.height - ARROW_MARGIN); + + context.quadraticCurveTo(thrusterCanvas.width - ARROW_MARGIN/2, + thrusterCanvas.height - ARROW_MARGIN/2, + thrusterCanvas.width - ARROW_MARGIN, + thrusterCanvas.height - ARROW_MARGIN/2); + + context.lineTo( ARROW_MARGIN/2, + thrusterCanvas.height/2 + ARROW_MARGIN/2); + + context.quadraticCurveTo(ARROW_MARGIN/2 - 6, + thrusterCanvas.height/2, + ARROW_MARGIN, thrusterCanvas.height/2 - ARROW_MARGIN/2); + + context.lineTo( thrusterCanvas.width - ARROW_MARGIN, + ARROW_MARGIN/2); + + context.quadraticCurveTo(thrusterCanvas.width - ARROW_MARGIN, + ARROW_MARGIN/2, thrusterCanvas.width - ARROW_MARGIN/2, + ARROW_MARGIN/2); + context.fill(); + + context.shadowColor = 'rgba(0,0,0,1.0)'; + context.shadowBlur = 8; + context.shadowOffsetX = 4; + context.shadowOffsetY = 4; + + context.stroke(); + context.restore(); +} + +// Animation functions........................................... + +function calculateFps(time) { + var fps = 1000 / (time - lastTime); + lastTime = time; + return fps; +} + +function animate(time) { + fps = calculateFps(time); + + context.clearRect(0,0,canvas.width,canvas.height); + drawGrid('lightgray', 10, 10); + + ball.update(context, time); + ledge.update(context, time); + + ledge.paint(context); + ball.paint(context); + + paintThruster(); + + window.requestNextAnimationFrame(animate); +} + +// Event handlers................................................ + +thrusterCanvas.onmousedown = function canvasMouseDown(e) { + var rect = thrusterCanvas.getBoundingClientRect(), + x = e.x || e.clientX, + y = e.y || e.clientY; + + e.preventDefault(); + e.stopPropagation(); + + pushBallLeft(); +}; + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + + +// Initialization................................................ + +thrusterContext.strokeStyle = 'rgba(100,140,230,0.6)'; +thrusterContext.shadowColor = 'rgba(0,0,0,0.3)'; +thrusterContext.shadowBlur = 6; +thrusterContext.shadowX = 4; +thrusterContext.shadowY = 4; + +ball.left = LEDGE_LEFT + LEDGE_WIDTH/2 - BALL_RADIUS; +ball.top = LEDGE_TOP - BALL_RADIUS*2; +ball.width = BALL_RADIUS*2; +ball.height = BALL_RADIUS*2; + +ball.velocityX = 110; +ball.velocityY = 0; + +ledge.left = LEDGE_LEFT; +ledge.top = LEDGE_TOP; +ledge.width = LEDGE_WIDTH; + +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch07/example-7.3/example.html b/canvas/ch07/example-7.3/example.html new file mode 100644 index 0000000..7e1fcd0 --- /dev/null +++ b/canvas/ch07/example-7.3/example.html @@ -0,0 +1,94 @@ + + + + + + Bucket + + + + + + + Canvas not supported + + +
0
+ +
+ Launch velocity (m/s): m/s
+ Launch angle (degrees): degrees
+
+ + + + + + diff --git a/canvas/ch07/example-7.3/example.js b/canvas/ch07/example-7.3/example.js new file mode 100644 index 0000000..c81ada7 --- /dev/null +++ b/canvas/ch07/example-7.3/example.js @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + scoreboard = document.getElementById('scoreboard'), + launchVelocityOutput = document.getElementById('launchVelocityOutput'), + launchAngleOutput = document.getElementById('launchAngleOutput'), + + launchTime = undefined, + + score = 0, + lastScore = 0, + lastMouse = { left: 0, top: 0 }, + + threePointer = false, + needInstructions = true, + + LAUNCHPAD_X = 50, + LAUNCHPAD_Y = context.canvas.height-50, + LAUNCHPAD_WIDTH = 50, + LAUNCHPAD_HEIGHT = 12, + BALL_RADIUS = 8, + ARENA_LENGTH_IN_METERS = 10, + INITIAL_LAUNCH_ANGLE = Math.PI/4, + + launchAngle = INITIAL_LAUNCH_ANGLE, + pixelsPerMeter = canvas.width / ARENA_LENGTH_IN_METERS, + + // LaunchPad................................................. + + launchPadPainter = { + LAUNCHPAD_FILL_STYLE: 'rgb(100,140,230)', + + paint: function (ledge, context) { + context.save(); + context.fillStyle = this.LAUNCHPAD_FILL_STYLE; + context.fillRect(LAUNCHPAD_X, LAUNCHPAD_Y, + LAUNCHPAD_WIDTH, LAUNCHPAD_HEIGHT); + context.restore(); + } + }, + + launchPad = new Sprite('launchPad', launchPadPainter), + + // Ball...................................................... + + ballPainter = { + BALL_FILL_STYLE: 'rgb(255,255,0)', + BALL_STROKE_STYLE: 'rgb(0,0,0,0.4)', + + paint: function (ball, context) { + context.save(); + context.shadowColor = undefined; + context.lineWidth = 2; + context.fillStyle = this.BALL_FILL_STYLE; + context.strokeStyle = this.BALL_STROKE_STYLE; + + context.beginPath(); + context.arc(ball.left, ball.top, + ball.radius, 0, Math.PI*2, false); + + context.clip(); + context.fill(); + context.stroke(); + context.restore(); + } + }, + + // Lob behavior.............................................. + + lob = { + lastTime: 0, + GRAVITY_FORCE: 9.81, // m/s/s + + applyGravity: function (elapsed) { + ball.velocityY = (this.GRAVITY_FORCE * elapsed) - + (launchVelocity * Math.sin(launchAngle)); + }, + + updateBallPosition: function (updateDelta) { + ball.left += ball.velocityX * (updateDelta) * pixelsPerMeter; + ball.top += ball.velocityY * (updateDelta) * pixelsPerMeter; + }, + + checkForThreePointer: function () { + if (ball.top < 0) { + threePointer = true; + } + }, + + checkBallBounds: function () { + if (ball.top > canvas.height || ball.left > canvas.width) { + reset(); + } + }, + + execute: function (ball, context, time) { + var updateDelta, + elapsedFlightTime; + + if (ballInFlight) { + if (launchTime === undefined) launchTime = time; + elapsedFrameTime = (time - this.lastTime)/1000, + elapsedFlightTime = (time - launchTime)/1000; + + this.applyGravity(elapsedFlightTime); + this.updateBallPosition(elapsedFrameTime); + this.checkForThreePointer(); + this.checkBallBounds(); + } + this.lastTime = time; + } + }, + + ball = new Sprite('ball', ballPainter, [ lob ]), + ballInFlight = false, + + // Bucket.................................................... + + catchBall = { + ballInBucket: function() { + return ball.left > bucket.left + bucket.width/2 && + ball.left < bucket.left + bucket.width && + ball.top > bucket.top && ball.top < bucket.top + bucket.height/3; + }, + + adjustScore: function() { + if (threePointer) lastScore = 3; + else lastScore = 2; + + score += lastScore; + scoreboard.innerHTML = score; + }, + + execute: function (bucket, context, time) { + if (ballInFlight && this.ballInBucket()) { + reset(); + this.adjustScore(); + } + } + }, + + BUCKET_X = 668, + BUCKET_Y = canvas.height - 100, + bucketImage = new Image(), + + bucket = new Sprite('bucket', + { + paint: function (sprite, context) { + context.drawImage(bucketImage, BUCKET_X, BUCKET_Y); + } + }, + + [ catchBall ] + ); + +// Functions..................................................... + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +function reset() { + ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2; + ball.top = LAUNCHPAD_Y - ball.height/2; + ball.velocityX = 0; + ball.velocityY = 0; + ballInFlight = false; + needInstructions = false; + lastScore = 0; +} + +function showText(text) { + var metrics; + + context.font = '42px Helvetica'; + metrics = context.measureText(text); + + context.save(); + context.shadowColor = undefined; + context.strokeStyle = 'rgb(80,120,210)'; + context.fillStyle = 'rgba(100,140,230,0.5)'; + + context.fillText(text, + canvas.width/2 - metrics.width/2, + canvas.height/2); + + context.strokeText(text, + canvas.width/2 - metrics.width/2, + canvas.height/2); + context.restore(); +} + +function drawGuidewire() { + context.moveTo(ball.left, ball.top); + context.lineTo(lastMouse.left, lastMouse.top); + context.stroke(); +}; + +function updateBackgroundText() { + if (lastScore == 3) showText('Three pointer!'); + else if (lastScore == 2) showText('Nice shot!'); + else if (needInstructions) showText('Click to launch ball'); +}; + +function resetScoreLater() { + setTimeout(function () { + lastScore = 0; + }, 1000); +}; + +function updateSprites(time) { + bucket.update(context, time); + launchPad.update(context, time); + ball.update(context, time); +} + +function paintSprites() { + launchPad.paint(context); + bucket.paint(context); + ball.paint(context); +} + +// Event handlers................................................ + +canvas.onmousedown = function(e) { + var rect; + + e.preventDefault(); + + if ( ! ballInFlight) { + ball.velocityX = launchVelocity * Math.cos(launchAngle); + ball.velocityY = launchVelocity * Math.sin(launchAngle); + ballInFlight = true; + threePointer = false; + launchTime = undefined; + } +}; + +canvas.onmousemove = function (e) { + var rect; + + e.preventDefault(); + + if ( ! ballInFlight) { + loc = windowToCanvas(e.clientX, e.clientY); + lastMouse.left = loc.x; + lastMouse.top = loc.y; + + deltaX = Math.abs(lastMouse.left - ball.left); + deltaY = Math.abs(lastMouse.top - ball.top); + + launchAngle = Math.atan(parseFloat(deltaY) / parseFloat(deltaX)); + launchVelocity = 4 * deltaY / Math.sin(launchAngle) / pixelsPerMeter; + + launchVelocityOutput.innerHTML = launchVelocity.toFixed(2); + launchAngleOutput.innerHTML = (launchAngle * 180/Math.PI).toFixed(2); + } +}; + +// Animation Loop................................................ + +function animate(time) { + context.clearRect(0,0,canvas.width,canvas.height); + + if (!ballInFlight) { + drawGuidewire(); + updateBackgroundText(); + + if (lastScore !== 0) { // just scored + resetScoreLater(); + } + } + + updateSprites(time); + paintSprites(); + + window.requestNextAnimationFrame(animate); +} + +// Initialization................................................ + +ball.width = BALL_RADIUS*2; +ball.height = ball.width; +ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2; +ball.top = LAUNCHPAD_Y - ball.height/2; +ball.radius = BALL_RADIUS; + +context.lineWidth = 0.5; +context.strokeStyle = 'rgba(0,0,0,0.5)'; +context.shadowColor = 'rgba(0,0,0,0.5)'; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; +context.stroke(); + +bucketImage.src = '../../shared/images/bucket.png'; +bucketImage.onload = function (e) { + bucket.left = BUCKET_X; + bucket.top = BUCKET_Y; + bucket.width = bucketImage.width; + bucket.height = bucketImage.height; +}; + +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch07/example-7.5/example.html b/canvas/ch07/example-7.5/example.html new file mode 100644 index 0000000..73069a8 --- /dev/null +++ b/canvas/ch07/example-7.5/example.html @@ -0,0 +1,59 @@ + + + + + + Pendulum + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch07/example-7.5/example.js b/canvas/ch07/example-7.5/example.js new file mode 100644 index 0000000..96fcb90 --- /dev/null +++ b/canvas/ch07/example-7.5/example.js @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + startTime = undefined, + + PIVOT_Y = 20, + PIVOT_RADIUS = 7, + WEIGHT_RADIUS = 25, + INITIAL_ANGLE = Math.PI/5, + ROD_LENGTH_IN_PIXELS = 300, + + // Pendulum painter........................................... + + pendulumPainter = { + PIVOT_FILL_STYLE: 'rgba(0,0,0,0.2)', + WEIGHT_SHADOW_COLOR: 'rgb(0,0,0)', + PIVOT_SHADOW_COLOR: 'rgb(255,255,0)', + STROKE_COLOR: 'rgb(100,100,195)', + + paint: function (pendulum, context) { + this.drawPivot(pendulum); + this.drawRod(pendulum); + this.drawWeight(pendulum, context); + }, + + drawWeight: function (pendulum, context) { + context.save(); + context.beginPath(); + context.arc(pendulum.weightX, pendulum.weightY, + pendulum.weightRadius, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = this.WEIGHT_SHADOW_COLOR; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.lineWidth = 2; + context.strokeStyle = this.STROKE_COLOR; + context.stroke(); + + context.beginPath(); + context.arc(pendulum.weightX, pendulum.weightY, + pendulum.weightRadius/2, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = this.PIVOT_SHADOW_COLOR; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + context.stroke(); + + context.restore(); + }, + + drawPivot: function (pendulum) { + context.save(); + context.beginPath(); + context.shadowColor = undefined; + context.shadowBlur = undefined; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.fillStyle = 'white'; + context.arc(pendulum.x + pendulum.pivotRadius, + pendulum.y, pendulum.pivotRadius/2, 0, Math.PI*2, false); + context.fill(); + context.stroke(); + + context.beginPath(); + context.fillStyle = this.PIVOT_FILL_STYLE; + context.arc(pendulum.x + pendulum.pivotRadius, + pendulum.y, pendulum.pivotRadius, 0, Math.PI*2, false); + context.fill(); + context.stroke(); + context.restore(); + }, + + drawRod: function (pendulum) { + context.beginPath(); + + context.moveTo( + pendulum.x + pendulum.pivotRadius + + pendulum.pivotRadius*Math.sin(pendulum.angle), + + pendulum.y + pendulum.pivotRadius*Math.cos(pendulum.angle)); + + context.lineTo( + pendulum.weightX - pendulum.weightRadius*Math.sin(pendulum.angle), + pendulum.weightY - pendulum.weightRadius*Math.cos(pendulum.angle)); + + context.stroke(); + } + }, + + // Swing behavior............................................. + + swing = { + GRAVITY_FORCE: 32, // 32 ft/s/s, + ROD_LENGTH: 0.8, // 0.8 ft + + execute: function(pendulum, context, time) { + var elapsedTime = (time - startTime) / 1000; + + pendulum.angle = pendulum.initialAngle * Math.cos( + Math.sqrt(this.GRAVITY_FORCE/this.ROD_LENGTH) * elapsedTime); + + pendulum.weightX = pendulum.x + + Math.sin(pendulum.angle) * pendulum.rodLength; + + pendulum.weightY = pendulum.y + + Math.cos(pendulum.angle) * pendulum.rodLength; + } + }, + + // Pendulum................................................... + + pendulum = new Sprite('pendulum', pendulumPainter, [ swing ]); + +// Animation Loop................................................ + +function animate(time) { + context.clearRect(0,0,canvas.width,canvas.height); + drawGrid('lightgray', 10, 10); + pendulum.update(context, time); + pendulum.paint(context); + window.requestNextAnimationFrame(animate); +} + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + context.stroke(); + } + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.beginPath(); + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + context.stroke(); + } + + context.restore(); +} + +// Initialization................................................ + +pendulum.x = canvas.width/2; +pendulum.y = PIVOT_Y; +pendulum.weightRadius = WEIGHT_RADIUS; +pendulum.pivotRadius = PIVOT_RADIUS; +pendulum.initialAngle = INITIAL_ANGLE; +pendulum.angle = INITIAL_ANGLE; +pendulum.rodLength = ROD_LENGTH_IN_PIXELS; + +context.lineWidth = 0.5; +context.strokeStyle = 'rgba(0,0,0,0.5)'; + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(0,0,0,0.5)'; + +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; +context.stroke(); + +startTime = + new Date(); +animate(startTime); + +drawGrid('lightgray', 10, 10); diff --git a/canvas/ch07/example-7.8/example.html b/canvas/ch07/example-7.8/example.html new file mode 100644 index 0000000..890a998 --- /dev/null +++ b/canvas/ch07/example-7.8/example.html @@ -0,0 +1,117 @@ + + + + + Non-linear motion + + + + + + + Canvas not supported + + +
+
+ Linear
+ Ease In
+ Ease Out
+ Ease In/Out
+ Elastic
+ Bounce
+
+
+ +
+ + canvas not supported + +
+ + + + + + + + diff --git a/canvas/ch07/example-7.8/example.js b/canvas/ch07/example-7.8/example.js new file mode 100644 index 0000000..b5e131f --- /dev/null +++ b/canvas/ch07/example-7.8/example.js @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + linearCheckbox = document.getElementById('linearCheckbox'), + easeInCheckbox = document.getElementById('easeInCheckbox'), + easeOutCheckbox = document.getElementById('easeOutCheckbox'), + easeInOutCheckbox = document.getElementById('easeInOutCheckbox'), + elasticCheckbox = document.getElementById('elasticCheckbox'), + bounceCheckbox = document.getElementById('bounceCheckbox'), + + thrustersCanvas = document.getElementById('thrustersCanvas'), + thrustersContext = thrustersCanvas.getContext('2d'), + + RIGHT = 1, + LEFT = 2, + ARROW_MARGIN = 10, + BALL_RADIUS = 25, + LEDGE_LEFT = 62, + LEDGE_TOP = 275, + LEDGE_WIDTH = canvas.width-(LEDGE_LEFT*2), + LEDGE_HEIGHT = 12, + PUSH_ANIMATION_DURATION = 3600, + + THRUSTER_FILL_STYLE = 'rgba(100,140,230,0.8)', + THRUSTER_FIRING_FILL_STYLE = 'rgba(255,255,0,0.8)', + + lastTime = 0, + arrow = LEFT, + + linear = AnimationTimer.makeLinear(), + easeIn = AnimationTimer.makeEaseIn(1), + easeOut = AnimationTimer.makeEaseOut(1), + easeInOut = AnimationTimer.makeEaseInOut(), + elastic = AnimationTimer.makeElastic(4), + bounce = AnimationTimer.makeBounce(5), + + pushAnimationTimer = new AnimationTimer(PUSH_ANIMATION_DURATION), + ballLocations = [], + + // Move ball behavior........................................ + + moveBall = { + lastTime: undefined, + + resetBall: function () { + ball.left = LEDGE_LEFT - BALL_RADIUS; + ball.top = LEDGE_TOP - BALL_RADIUS*2; + }, + + updateBallPosition: function (elapsed) { + if (arrow === LEFT) ball.left -= ball.velocityX * (elapsed/1000); + else ball.left += ball.velocityX * (elapsed/1000); + }, + + execute: function (ball, context, time) { + if (pushAnimationTimer.isRunning()) { + var animationElapsed = pushAnimationTimer.getElapsedTime(), + elapsed; + + if (this.lastTime !== undefined) { + elapsed = animationElapsed - this.lastTime; + + this.updateBallPosition(elapsed); + ballLocations.push(ball.left); + + if (isBallOnLedge()) { + if (pushAnimationTimer.isOver()) { + pushAnimationTimer.stop(); + } + } + else { // ball fell off the ledge + pushAnimationTimer.stop(); + this.resetBall(); + } + } + } + this.lastTime = animationElapsed; + } + }, + + // Ball sprite............................................... + + ball = new Sprite('ball', + { + paint: function (sprite, context) { + context.save(); + context.beginPath(); + context.arc(sprite.left + sprite.width/2, sprite.top + sprite.height/2, + BALL_RADIUS, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgb(0,0,255)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.lineWidth = 2; + context.strokeStyle = 'rgb(100,100,195)'; + context.stroke(); + + context.beginPath(); + context.arc(sprite.left + sprite.width/2, sprite.top + sprite.height/2, + BALL_RADIUS/2, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgb(255,255,0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + context.stroke(); + + context.restore(); + } + }, + + [ moveBall ] + ), + + ledge = new Sprite('ledge', + { + paint: function (sprite, context) { + context.save(); + context.shadowColor = 'rgba(0,0,0,0.8)'; + context.shadowBlur = 8; + context.shadowOffsetX = 4; + context.shadowOffsetY = 4; + + context.fillStyle = 'rgba(255,255,0,0.6)'; + context.fillRect(sprite.left,sprite.top, + sprite.width,sprite.height); + context.restore(); + } + } + ); + +// Behavior functions............................................ + +function paintBall(sprite, left) { + context.save(); + context.beginPath(); + context.arc(left + sprite.width/2, sprite.top + sprite.height/2, + BALL_RADIUS, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgb(0,0,255)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.lineWidth = 2; + context.strokeStyle = 'rgb(100,100,195)'; + context.stroke(); + + context.beginPath(); + context.arc(left + sprite.width/2, sprite.top + sprite.height/2, + BALL_RADIUS/2, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgb(255,255,0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + context.stroke(); + + context.restore(); +} + +function restartAnimationTimer() { + if (pushAnimationTimer.isRunning()) { + pushAnimationTimer.stop(); + } + pushAnimationTimer.start(); +} + +function pushBallLeft() { + arrow = LEFT; + restartAnimationTimer(); +} + +function pushBallRight() { + arrow = RIGHT; + restartAnimationTimer(); +} + +function isBallOnLedge() { + return ball.left + 2*BALL_RADIUS > LEDGE_LEFT && + ball.left < LEDGE_LEFT + LEDGE_WIDTH; +} + +// Paint functions............................................... + +function paintThrusters() { + thrustersContext.clearRect(0,0, + thrustersCanvas.width,thrustersCanvas.height); + + if (arrow === LEFT) { + thrustersContext.fillStyle = + pushAnimationTimer.isRunning() ? THRUSTER_FIRING_FILL_STYLE : + THRUSTER_FILL_STYLE; + paintLeftArrow(thrustersContext); + thrustersContext.fillStyle = THRUSTER_FILL_STYLE; + paintRightArrow(thrustersContext); + } + else { + thrustersContext.fillStyle = + pushAnimationTimer.isRunning() ? THRUSTER_FIRING_FILL_STYLE : + THRUSTER_FILL_STYLE; + paintRightArrow(thrustersContext); + thrustersContext.fillStyle = THRUSTER_FILL_STYLE; + paintLeftArrow(thrustersContext); + } +} + +function paintRightArrow(context) { + thrustersContext.save(); + thrustersContext.translate(thrustersCanvas.width, 0); + thrustersContext.scale(-1,1); + paintArrow(context); + thrustersContext.restore(); +} + +function paintLeftArrow(context) { + paintArrow(context); +} + +function paintArrow(context) { + context.beginPath(); + + context.moveTo( thrustersCanvas.width/2 - ARROW_MARGIN/2, + ARROW_MARGIN/2); + + context.lineTo( thrustersCanvas.width/2 - ARROW_MARGIN/2, + thrustersCanvas.height - ARROW_MARGIN); + + context.quadraticCurveTo(thrustersCanvas.width/2 - ARROW_MARGIN/2, + thrustersCanvas.height - ARROW_MARGIN/2, + thrustersCanvas.width/2 - ARROW_MARGIN, + thrustersCanvas.height - ARROW_MARGIN/2); + + context.lineTo( ARROW_MARGIN, + thrustersCanvas.height/2 + ARROW_MARGIN/2); + + context.quadraticCurveTo(ARROW_MARGIN - 3, + thrustersCanvas.height/2, + ARROW_MARGIN, thrustersCanvas.height/2 - ARROW_MARGIN/2); + + context.lineTo( thrustersCanvas.width/2 - ARROW_MARGIN, + ARROW_MARGIN/2); + + context.quadraticCurveTo(thrustersCanvas.width/2 - ARROW_MARGIN, + ARROW_MARGIN/2, thrustersCanvas.width/2 - ARROW_MARGIN/2, + ARROW_MARGIN/2); + context.fill(); + context.stroke(); +} + +// Event handlers................................................ + +thrustersCanvas.onmousedown = function canvasMouseDown(e) { + var rect = thrustersCanvas.getBoundingClientRect(), + x = e.x || e.clientX, + y = e.y || e.clientY; + + e.preventDefault(); + e.stopPropagation(); + + ballLocations = []; + + if (x-rect.left > thrustersCanvas.width/2) + pushBallRight(); + else + pushBallLeft(); +}; + +linearCheckbox.onchange = function (e) { + pushAnimationTimer.timeWarp = linear; +}; + +easeInCheckbox.onchange = function (e) { + pushAnimationTimer.timeWarp = easeIn; +}; + +easeOutCheckbox.onchange = function (e) { + pushAnimationTimer.timeWarp = easeOut; +}; + +easeInOutCheckbox.onchange = function (e) { + pushAnimationTimer.timeWarp = easeInOut; +}; + +elasticCheckbox.onchange = function (e) { + pushAnimationTimer.timeWarp = elastic; + ball.left = LEDGE_LEFT - BALL_RADIUS; + ball.top = LEDGE_TOP - BALL_RADIUS*2; +}; + +bounceCheckbox.onchange = function (e) { + pushAnimationTimer.timeWarp = bounce; +}; + +linearCheckbox.onchange = function (e) { + pushAnimationTimer.timeWarp = linear; +}; + +// AnimationTimer functions........................................... + +function calculateFps(time) { + var fps = 1000 / (time - lastTime); + lastTime = time; + return fps; +} + +var framecnt = 0; + +function animate(time) { + fps = calculateFps(time); + + context.clearRect(0,0,canvas.width,canvas.height); + + ball.update(context, time); + ball.paint(context); + + ledge.update(context, time); + ledge.paint(context); +/* +ballLocations.forEach(function(loc) { + paintBall(ball, loc); +}); + */ + paintThrusters(); + + window.requestNextAnimationFrame(animate); +} + +// Initialization................................................ + +thrustersContext.strokeStyle = 'rgba(100,140,230,0.6)'; +thrustersContext.shadowColor = 'rgba(0,0,0,0.3)'; +thrustersContext.shadowBlur = 6; +thrustersContext.shadowX = 4; +thrustersContext.shadowY = 4; + +window.requestNextAnimationFrame(animate); + +ball.left = LEDGE_LEFT - BALL_RADIUS; +ball.top = LEDGE_TOP - BALL_RADIUS*2; +ball.width = BALL_RADIUS*2; +ball.height = BALL_RADIUS*2; +ball.velocityX = 100; +ball.velocityY = 0; + +ledge.left = LEDGE_LEFT; +ledge.top = LEDGE_TOP; +ledge.width = LEDGE_WIDTH; diff --git a/canvas/ch07/example-7.9/example.html b/canvas/ch07/example-7.9/example.html new file mode 100644 index 0000000..2a0f40c --- /dev/null +++ b/canvas/ch07/example-7.9/example.html @@ -0,0 +1,87 @@ + + + + + + Warping Time + + + + + + + + + Canvas not supported + + +
+
+ Linear + Ease In + Ease Out + Ease In/Out +
+
+ + + + + + + + diff --git a/canvas/ch07/example-7.9/example.js b/canvas/ch07/example-7.9/example.js new file mode 100644 index 0000000..4fb40ad --- /dev/null +++ b/canvas/ch07/example-7.9/example.js @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + linearRadio = document.getElementById('linearRadio'), + easeInRadio = document.getElementById('easeInRadio'), + easeOutRadio = document.getElementById('easeOutRadio'), + easeInOutRadio = document.getElementById('easeInOutRadio'), + + animateButton = document.getElementById('animateButton'), + spritesheet = new Image(), + runnerCells = [ + { left: 0, top: 0, width: 47, height: 64 }, + { left: 55, top: 0, width: 44, height: 64 }, + { left: 107, top: 0, width: 39, height: 64 }, + { left: 152, top: 0, width: 46, height: 64 }, + { left: 208, top: 0, width: 49, height: 64 }, + { left: 265, top: 0, width: 46, height: 64 }, + { left: 320, top: 0, width: 42, height: 64 }, + { left: 380, top: 0, width: 35, height: 64 }, + { left: 425, top: 0, width: 35, height: 64 }, + ], + + interval, + lastAdvance = 0.0, + + SPRITE_LEFT = canvas.width - runnerCells[0].width; + SPRITE_TOP = 10, + + PAGEFLIP_INTERVAL = 100, + ANIMATION_DURATION = 3900, + + animationTimer = new AnimationTimer(ANIMATION_DURATION, AnimationTimer.makeLinear(1)), + + LEFT = 1.5, + RIGHT = canvas.width - runnerCells[0].width, + BASELINE = canvas.height - 9.5, + TICK_HEIGHT = 8.5, + WIDTH = RIGHT-LEFT, + + runInPlace = { + execute: function(sprite, context, time) { + var elapsed = animationTimer.getElapsedTime(); + + if (lastAdvance === 0) { // skip first time + lastAdvance = elapsed; + } + else if (lastAdvance !== 0 && elapsed - lastAdvance > PAGEFLIP_INTERVAL) { + sprite.painter.advance(); + lastAdvance = elapsed; + } + } + }, + + moveRightToLeft = { + lastMove: 0, + reset: function () { + this.lastMove = 0; + }, + + execute: function(sprite, context, time) { + var elapsed = animationTimer.getElapsedTime(), + advanceElapsed = elapsed - this.lastMove; + + if (this.lastMove === 0) { // skip first time + this.lastMove = elapsed; + } + else { + sprite.left -= (advanceElapsed / 1000) * sprite.velocityX; + this.lastMove = elapsed; + } + } + }, + + sprite = new Sprite('runner', + new SpriteSheetPainter(runnerCells), + [ moveRightToLeft, runInPlace ]); + +// Functions..................................................... + +function endAnimation() { + animateButton.value = 'Animate'; + animateButton.style.display = 'inline'; + animationTimer.stop(); + + lastAdvance = 0; + sprite.painter.cellIndex = 0; + sprite.left = SPRITE_LEFT; + animationTimer.reset(); + moveRightToLeft.reset(); +} + +function startAnimation() { + animationTimer.start(); + animateButton.style.display = 'none'; + window.requestNextAnimationFrame(animate); +} + +function drawAxis() { + + context.lineWidth = 0.5; + context.strokeStyle = 'cornflowerblue'; + + context.moveTo(LEFT, BASELINE); + context.lineTo(RIGHT, BASELINE); + context.stroke(); + + for (var i=0; i <= WIDTH; i+=WIDTH/20) { + context.beginPath(); + context.moveTo(LEFT + i, BASELINE-TICK_HEIGHT/2); + context.lineTo(LEFT + i, BASELINE+TICK_HEIGHT/2); + context.stroke(); + } + + for (i=0; i < WIDTH; i+=WIDTH/4) { + context.beginPath(); + context.moveTo(LEFT + i, BASELINE-TICK_HEIGHT); + context.lineTo(LEFT + i, BASELINE+TICK_HEIGHT); + context.stroke(); + } + + context.beginPath(); + context.moveTo(RIGHT, BASELINE-TICK_HEIGHT); + context.lineTo(RIGHT, BASELINE+TICK_HEIGHT); + context.stroke(); +} + +function drawTimeline() { + var realElapsed = animationTimer.getRealElapsedTime(), + realPercent = realElapsed / ANIMATION_DURATION; + + context.lineWidth = 0.5; + context.strokeStyle = 'rgba(0, 0, 255, 0.5)'; + + context.beginPath(); + + context.moveTo(WIDTH - realPercent*(WIDTH), 0); + context.lineTo(WIDTH - realPercent*(WIDTH), canvas.height); + context.stroke(); +} + +// Event handlers................................................ + +animateButton.onclick = function (e) { + if (animateButton.value === 'Animate') startAnimation(); + else endAnimation(); +}; + +linearRadio.onclick = function (e) { + animationTimer.timeWarp = AnimationTimer.makeLinear(1); +}; + +easeInRadio.onclick = function (e) { + animationTimer.timeWarp = AnimationTimer.makeEaseIn(1); +}; + +easeOutRadio.onclick = function (e) { + animationTimer.timeWarp = AnimationTimer.makeEaseOut(1); +}; + +easeInOutRadio.onclick = function (e) { + animationTimer.timeWarp = AnimationTimer.makeEaseInOut(); +}; + +// Animation..................................................... + +function animate(time) { + if (animationTimer.isRunning()) { + elapsed = animationTimer.getElapsedTime(); + + context.clearRect(0,0,canvas.width,canvas.height); + sprite.update(context, time); + sprite.paint(context); + + drawTimeline(); + drawAxis(); + + if (animationTimer.isOver()) { + endAnimation(); + } + window.requestNextAnimationFrame(animate); + } +} + +// Initialization................................................ + +spritesheet.src = '../../shared/images/running-sprite-sheet.png'; +sprite.left = SPRITE_LEFT; +sprite.top = SPRITE_TOP; +sprite.velocityX = 100; // pixels/second +drawAxis(); +spritesheet.onload = function () { + sprite.paint(context); +}; diff --git a/canvas/ch07/tweening/example.html b/canvas/ch07/tweening/example.html new file mode 100644 index 0000000..5a81b86 --- /dev/null +++ b/canvas/ch07/tweening/example.html @@ -0,0 +1,87 @@ + + + + + + Warping Time + + + + + + + + + Canvas not supported + + +
+
+ Linear + Ease In + Ease Out + Ease In/Out +
+
+ + + + + + + + diff --git a/canvas/ch08/example-8.1/example.html b/canvas/ch08/example-8.1/example.html new file mode 100644 index 0000000..64e6075 --- /dev/null +++ b/canvas/ch08/example-8.1/example.html @@ -0,0 +1,57 @@ + + + + + + Bouncing off the walls + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch08/example-8.1/example.js b/canvas/ch08/example-8.1/example.js new file mode 100644 index 0000000..c682ba8 --- /dev/null +++ b/canvas/ch08/example-8.1/example.js @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + ball = new Sprite('ball', + { + paint: function (sprite, context) { + context.save(); + context.strokeStyle = 'blue'; + context.fillStyle = 'yellow'; + context.beginPath(); + context.arc(sprite.left + sprite.width/2, + sprite.top + sprite.height/2, + 10, 0, Math.PI*2, false); + context.stroke(); + context.fill(); + context.restore(); + } + }), + ballMoving = false, + lastTime = undefined, + velocityX = 350, + velocityY = 190, + showInstructions = true; + +// Functions..................................................... + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function getBoundingBox(ball) { + return { left: ball.left, + top: ball.top, + width: ball.width, + height: ball.height + }; +} + +function handleEdgeCollisions() { + var bbox = getBoundingBox(ball), + right = bbox.left + bbox.width, + bottom = bbox.top + bbox.height; + + if (right > canvas.width || bbox.left < 0) { + velocityX = -velocityX; + + if (right > canvas.width) { + ball.left -= right-canvas.width; + } + + if (bbox.left < 0) { + ball.left -= bbox.left; + } + } + if (bottom > canvas.height || bbox.top < 0) { + velocityY = -velocityY; + + if (bottom > canvas.height) { + ball.top -= bottom-canvas.height; + } + if (bbox.top < 0) { + ball.top -= bbox.top; + } + } +}; + +function detectCollisions() { + if (ballMoving) { + handleEdgeCollisions(); + } +}; + +function isPointInBall(x, y) { + return x > ball.left && x < ball.left + ball.width && + y > ball.top && y < ball.top + ball.height; +} + + +// Event Handlers................................................ + +canvas.onmousedown = function (e) { + var location = windowToCanvas(e); + + ballMoving = !ballMoving; + + if (showInstructions) + showInstructions = false; +}; + +// Animation..................................................... + +function animate(time) { + var elapsedTime; + + if (lastTime === 0) { + lastTime = time; + } + else { + context.clearRect(0,0,canvas.width,canvas.height); + + if (ballMoving) { + elapsedTime = parseFloat(time - lastTime) / 1000; + + ball.left += velocityX * elapsedTime; + ball.top += velocityY * elapsedTime; + + detectCollisions(); + } + + lastTime = time; + + ball.paint(context); + + if (showInstructions) { + context.fillStyle = 'rgba(100, 140, 230, 0.7)'; + context.font = '24px Arial'; + context.fillText('Click anywhere to start or stop the ball', 20, 40); + } + } + window.requestNextAnimationFrame(animate); +}; + + +// Initialization................................................ + +ball.fillStyle = 'rgba(255,255,0,1.0)'; +ball.left = 100; +ball.top = 100; + +context.shadowColor = 'rgba(100,140,255,0.5)'; +context.shadowBlur = 4; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.font = '38px Arial'; + +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch08/example-8.10/example.html b/canvas/ch08/example-8.10/example.html new file mode 100644 index 0000000..aab635a --- /dev/null +++ b/canvas/ch08/example-8.10/example.html @@ -0,0 +1,57 @@ + + + + + + Polygon and Circle Collision Detection using SAT + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch08/example-8.10/example.js b/canvas/ch08/example-8.10/example.js new file mode 100644 index 0000000..d8d79c1 --- /dev/null +++ b/canvas/ch08/example-8.10/example.js @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + shapes = [], + polygonPoints = [ + [ new Point(250, 150), new Point(250, 250), + new Point(350, 250) ], + + [ new Point(100, 100), new Point(100, 150), + new Point(150, 150), new Point(150, 100) ], + + [ new Point(400, 100), new Point(380, 150), + new Point(500, 150), new Point(520, 100) ] + ], + + polygonStrokeStyles = [ 'blue', 'yellow', 'red'], + polygonFillStyles = [ 'rgba(255,255,0,0.7)', + 'rgba(100,140,230,0.6)', + 'rgba(255,255,255,0.8)' ], + + mousedown = { x: 0, y: 0 }, + lastdrag = { x: 0, y: 0 }, + shapeBeingDragged = undefined, + c1 = new Circle(150, 75, 20), + c2 = new Circle(350, 25, 30); + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function drawShapes() { + shapes.forEach( function (shape) { + shape.stroke(context); + shape.fill(context); + }); +} + +canvas.onmousedown = function (e) { + var location = windowToCanvas(e); + + shapes.forEach( function (shape) { + if (shape.isPointInPath(context, location.x, location.y)) { + shapeBeingDragged = shape; + mousedown.x = location.x; + mousedown.y = location.y; + lastdrag.x = location.x; + lastdrag.y = location.y; + } + }); +} + +function detectCollisions() { + var textY = 30; + + if (shapeBeingDragged) { + shapes.forEach( function (shape) { + if (shape !== shapeBeingDragged) { + if (shapeBeingDragged.collidesWith(shape)) { + context.fillStyle = shape.fillStyle; + context.fillText('collision', 20, textY); + textY += 40; + } + } + }); + } +}; + +canvas.onmousemove = function (e) { + var location, + dragVector; + + if (shapeBeingDragged !== undefined) { + location = windowToCanvas(e); + dragVector = { x: location.x - lastdrag.x, + y: location.y - lastdrag.y + }; + + shapeBeingDragged.move(dragVector.x, dragVector.y); + + lastdrag.x = location.x; + lastdrag.y = location.y; + + context.clearRect(0,0,canvas.width,canvas.height); + drawShapes(); + detectCollisions(); + } +} + +canvas.onmouseup = function (e) { + shapeBeingDragged = undefined; +} + +for (var i=0; i < polygonPoints.length; ++i) { + var polygon = new Polygon(), + points = polygonPoints[i]; + + polygon.strokeStyle = polygonStrokeStyles[i]; + polygon.fillStyle = polygonFillStyles[i]; + + points.forEach( function (point) { + polygon.addPoint(point.x, point.y); + }); + + shapes.push(polygon); +} + +shapes.push(c1); +shapes.push(c2); + +context.shadowColor = 'rgba(100,140,255,0.5)'; +context.shadowBlur = 4; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.font = '38px Arial'; + +drawShapes(); + +context.save(); +context.fillStyle = 'cornflowerblue'; +context.font = '24px Arial'; +context.fillText('Drag shapes over each other', 10, 25); +context.restore(); diff --git a/canvas/ch08/example-8.10/shapes.js b/canvas/ch08/example-8.10/shapes.js new file mode 100644 index 0000000..9e04e7c --- /dev/null +++ b/canvas/ch08/example-8.10/shapes.js @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +function getPolygonPointClosestToCircle(polygon, circle) { + var min = 10000, + length, + testPoint, + closestPoint; + + for (var i=0; i < polygon.points.length; ++i) { + testPoint = polygon.points[i]; + length = Math.sqrt(Math.pow(testPoint.x - circle.x, 2), + Math.pow(testPoint.y - circle.y, 2)); + if (length < min) { + min = length; + closestPoint = testPoint; + } + } + + return closestPoint; +}; + +function polygonCollidesWithCircle (polygon, circle) { + var min=10000, v1, v2, + edge, perpendicular, + axes = polygon.getAxes(), + closestPoint = getPolygonPointClosestToCircle(polygon, circle); + + v1 = new Vector(new Point(circle.x, circle.y)); + v2 = new Vector(new Point(closestPoint.x, closestPoint.y)); + + axes.push(v1.subtract(v2).normalize()); + + return !polygon.separationOnAxes(axes, circle); +}; + +var Point = function (x, y) { + this.x = x; + this.y = y; +}; + +var Shape = function () { + this.x = undefined; + this.y = undefined; + this.strokeStyle = 'rgba(255, 253, 208, 0.9)'; + this.fillStyle = 'rgba(147, 197, 114, 0.8)'; +}; + +Shape.prototype = { + + collidesWith: function (shape) { + var axes = this.getAxes().concat(shape.getAxes()); + return !this.separationOnAxes(axes, shape); + }, + + separationOnAxes: function (axes, shape) { + for (var i=0; i < axes.length; ++i) { + axis = axes[i]; + projection1 = shape.project(axis); + projection2 = this.project(axis); + + if (! projection1.overlaps(projection2)) { + return true; // don't have to test remaining axes + } + } + return false; + }, + + move: function (dx, dy) { + throw 'move(dx, dy) not implemented'; + }, + + createPath: function (context) { + throw 'createPath(context) not implemented'; + }, + + getAxes: function () { + throw 'getAxes() not implemented'; + }, + + project: function (axis) { + throw 'project(axis) not implemented'; + }, + + fill: function (context) { + context.save(); + context.fillStyle = this.fillStyle; + this.createPath(context); + context.fill(); + context.restore(); + }, + + stroke: function (context) { + context.save(); + context.strokeStyle = this.strokeStyle; + this.createPath(context); + context.stroke(); + context.restore(); + }, + + isPointInPath: function (context, x, y) { + this.createPath(context); + return context.isPointInPath(x, y); + }, +}; + +var Projection = function (min, max) { + this.min = min; + this.max = max; +}; + +Projection.prototype = { + overlaps: function (projection) { + return this.max > projection.min && projection.max > this.min; + } +}; + +var Vector = function(point) { + if (point === undefined) { + this.x = 0; + this.y = 0; + } + else { + this.x = point.x; + this.y = point.y; + } +}; + +Vector.prototype = { + getMagnitude: function () { + return Math.sqrt(Math.pow(this.x, 2) + + Math.pow(this.y, 2)); + }, + + dotProduct: function (vector) { + return this.x * vector.x + + this.y * vector.y; + }, + + add: function (vector) { + var v = new Vector(); + v.x = this.x + vector.x; + v.y = this.y + vector.y; + return v; + }, + + subtract: function (vector) { + var v = new Vector(); + v.x = this.x - vector.x; + v.y = this.y - vector.y; + return v; + }, + + normalize: function () { + var v = new Vector(0, 0), + m = this.getMagnitude(); + + if (m != 0) { + v.x = this.x / m; + v.y = this.y / m; + } + return v; + }, + + perpendicular: function () { + var v = new Vector(); + v.x = this.y; + v.y = 0-this.x; + return v; + }, + + edge: function (vector) { + return this.subtract(vector); + }, + + normal: function () { + var p = this.perpendicular(); + return p.normalize(); + } +}; + +var Polygon = function () { + this.points = []; + this.strokeStyle = 'blue'; + this.fillStyle = 'white'; +}; + +Polygon.prototype = new Shape(); + +Polygon.prototype.collidesWith = function (shape) { + var axes = shape.getAxes(); + + if (axes === undefined) { + return polygonCollidesWithCircle(this, shape); + } + else { + axes.concat(this.getAxes()); + return !this.separationOnAxes(axes, shape); + } +}; + +Polygon.prototype.getAxes = function () { + var v1, v2, edge, perpendicular, normal, axes = []; + + for (var i=0; i < this.points.length-1; i++) { + v1 = new Vector(this.points[i]); + v2 = new Vector(this.points[i+1]); + axes.push(v1.edge(v2).normal()); + } + + v1 = new Vector(this.points[this.points.length-1]); + v2 = new Vector(this.points[0]); + axes.push(v1.edge(v2).normal()); + + return axes; +}; + +Polygon.prototype.project = function (axis) { + var scalars = []; + + this.points.forEach( function (point) { + scalars.push(new Vector(point).dotProduct(axis)); + }); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Polygon.prototype.addPoint = function (x, y) { + this.points.push(new Point(x,y)); +}; + +Polygon.prototype.createPath = function (context) { + if (this.points.length === 0) + return; + + context.beginPath(); + context.moveTo(this.points[0].x, + this.points[0].y); + + for (var i=0; i < this.points.length; ++i) { + context.lineTo(this.points[i].x, + this.points[i].y); + } + + context.closePath(); +}; + +Polygon.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +Polygon.prototype.move = function (dx, dy) { + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +var Circle = function (x, y, radius) { + this.x = x; + this.y = y; + this.radius = radius; + this.strokeStyle = 'rgba(255, 253, 208, 0.9)'; + this.fillStyle = 'rgba(147, 197, 114, 0.8)'; +} + +Circle.prototype = new Shape(); + +Circle.prototype.collidesWith = function (shape) { + var point, length, min=10000, v1, v2, + edge, perpendicular, normal, + axes = shape.getAxes(), distance; + + if (axes === undefined) { // circle + distance = Math.sqrt(Math.pow(shape.x - this.x, 2) + + Math.pow(shape.y - this.y, 2)); + + return distance < Math.abs(this.radius + shape.radius); + } + else { // polygon + return polygonCollidesWithCircle(shape, this); + } +}; + +Circle.prototype.getAxes = function () { + return undefined; // there are an infinite number of axes for circles +}; + +Circle.prototype.project = function (axis) { + var scalars = [], + point = new Point(this.x, this.y); + dotProduct = new Vector(point).dotProduct(axis); + + scalars.push(dotProduct); + scalars.push(dotProduct + this.radius); + scalars.push(dotProduct - this.radius); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Circle.prototype.move = function (dx, dy) { + this.x += dx; + this.y += dy; +}; + +Circle.prototype.createPath = function (context) { + context.beginPath(); + context.arc(this.x, this.y, this.radius, 0, Math.PI*2, false); +}; diff --git a/canvas/ch08/example-8.19/example.html b/canvas/ch08/example-8.19/example.html new file mode 100644 index 0000000..a3c7512 --- /dev/null +++ b/canvas/ch08/example-8.19/example.html @@ -0,0 +1,57 @@ + + + + + + Sticking using SAT and MTV + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch08/example-8.19/example.js b/canvas/ch08/example-8.19/example.js new file mode 100644 index 0000000..651f8b0 --- /dev/null +++ b/canvas/ch08/example-8.19/example.js @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + shapes = [], + polygonPoints = [ + [ new Point(250, 150), new Point(250, 200), + new Point(300, 200) ], + + [ new Point(100, 100), new Point(100, 125), + new Point(125, 125), new Point(125, 100) ], + + [ new Point(400, 100), new Point(380, 150), + new Point(500, 150), new Point(520, 100) ], + ], + + polygonStrokeStyles = [ 'blue', 'yellow', 'red'], + polygonFillStyles = [ 'rgba(255,255,0,0.7)', + 'rgba(100,140,230,0.6)', + 'rgba(255,255,255,0.8)' ], + shapeMoving = undefined, + c1 = new Circle(150, 275, 20), + c2 = new Circle(350, 350, 30), + + lastTime = undefined, + velocity = { x: 350, y: 190 }, + lastVelocity = { x: 350, y: 190 }, + STICK_DELAY = 500, + stuck = false; + showInstructions = true; + +// Functions..................................................... + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function drawShapes() { + shapes.forEach( function (shape) { + shape.stroke(context); + shape.fill(context); + }); +} + +function stick(mtv) { + var dx, + dy, + velocityMagnitude, + point; + + if (mtv.axis === undefined) { // The object that's moving is a circle. + point = new Point(); + velocityMagnitude = Math.sqrt(Math.pow(velocity.x, 2) + + Math.pow(velocity.y, 2)); + + // Point the mtv axis in the direction of the circle's velocity. + + point.x = velocity.x / velocityMagnitude; + point.y = velocity.y / velocityMagnitude; + + mtv.axis = new Vector(point); + } + + // Calclulate delta X and delta Y. The mtv.axis is a unit vector + // indicating direction, and the overlap is the magnitude of + // the translation vector. + + dx = mtv.axis.x * mtv.overlap; + dy = mtv.axis.y * mtv.overlap; + + // If deltas and velocity are in the same direction, + // turn deltas around. + + if ((dx < 0 && velocity.x < 0) || (dx > 0 && velocity.x > 0)) + dx = -dx; + + if ((dy < 0 && velocity.y < 0) || (dy > 0 && velocity.y > 0)) + dy = -dy; + + // In STICK_DELAY (500) ms, move the moving shape out of collision + + setTimeout(function () { + shapeMoving.move(dx, dy); + }, STICK_DELAY); + + // Reset pertinent variables + + lastVelocity.x = velocity.x; + lastVelocity.y = velocity.y; + velocity.x = velocity.y = 0; + + // Don't stick again before STICK_DELAY expires + stuck = true; +} + +function collisionDetected(mtv) { + return mtv.axis != undefined || mtv.overlap !== 0; +} + +function detectCollisions() { + var textY = 30, bbox, mtv; + + if (shapeMoving) { + shapes.forEach( function (shape) { + if (shape !== shapeMoving) { + mtv = shapeMoving.collidesWith(shape); + + if (collisionDetected(mtv)) { + if (!stuck) + stick(mtv); + } + } + }); + + bbox = shapeMoving.boundingBox(); + if (bbox.left + bbox.width > canvas.width || bbox.left < 0) { + velocity.x = -velocity.x; + } + if (bbox.top + bbox.height > canvas.height || bbox.top < 0) { + velocity.y = -velocity.y; + } + } +}; + +// Event Handlers................................................ + +canvas.onmousedown = function (e) { + var location = windowToCanvas(e); + + if (showInstructions) + showInstructions = false; + + velocity.x = lastVelocity.x; + velocity.y = lastVelocity.y; + + shapeMoving = undefined; + stuck = false; + + shapes.forEach( function (shape) { + if (shape.isPointInPath(context, location.x, location.y)) { + shapeMoving = shape; + } + }); +}; + +// Animation..................................................... + +function animate(time) { + var elapsedTime, deltaX; + + if (lastTime === 0) { + if (time !== undefined) + lastTime = time; + + window.requestNextAnimationFrame(animate); + return; + } + + context.clearRect(0,0,canvas.width,canvas.height); + + if (shapeMoving !== undefined) { + elapsedTime = parseFloat(time - lastTime) / 1000; + shapeMoving.move(velocity.x * elapsedTime, + velocity.y * elapsedTime); + } + + detectCollisions(); + drawShapes(); + lastTime = time; + + if (showInstructions) { + context.fillStyle = 'cornflowerblue'; + context.font = '24px Arial'; + context.fillText('Click on a shape to animate it', 20, 40); + } + window.requestNextAnimationFrame(animate); +}; + +// Initialization................................................ + +for (var i=0; i < polygonPoints.length; ++i) { + var polygon = new Polygon(), + points = polygonPoints[i]; + + polygon.strokeStyle = polygonStrokeStyles[i]; + polygon.fillStyle = polygonFillStyles[i]; + + points.forEach( function (point) { + polygon.addPoint(point.x, point.y); + }); + + shapes.push(polygon); +} + +c1.fillStyle = 'rgba(200, 50, 50, 0.5)'; + +shapes.push(c1); +shapes.push(c2); + +context.shadowColor = 'rgba(100,140,255,0.5)'; +context.shadowBlur = 4; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.font = '38px Arial'; + +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch08/example-8.19/shapes.js b/canvas/ch08/example-8.19/shapes.js new file mode 100644 index 0000000..cf704eb --- /dev/null +++ b/canvas/ch08/example-8.19/shapes.js @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Functions..................................................... + +// .............................................................. +// Check to see if a polygon collides with another polygon +// .............................................................. + +function polygonCollidesWithPolygon (p1, p2, displacement) { // displacement for p1 + var mtv1 = p1.minimumTranslationVector(p1.getAxes(), p2, displacement), + mtv2 = p1.minimumTranslationVector(p2.getAxes(), p2, displacement); + + if (mtv1.overlap === 0 || mtv2.overlap === 0) + return { axis: undefined, overlap: 0 }; + else + return mtv1.overlap < mtv2.overlap ? mtv1 : mtv2; +}; + +// .............................................................. +// Check to see if a circle collides with another circle +// .............................................................. + +function circleCollidesWithCircle (c1, c2) { + var distance = Math.sqrt( Math.pow(c2.x - c1.x, 2) + + Math.pow(c2.y - c1.y, 2)), + overlap = Math.abs(c1.radius + c2.radius) - distance; + + return overlap < 0 ? + new MinimumTranslationVector(undefined, 0) : + new MinimumTranslationVector(undefined, overlap); +}; + +// .............................................................. +// Get the polygon's point that's closest to the circle +// .............................................................. + +function getPolygonPointClosestToCircle(polygon, circle) { + var min = BIG_NUMBER, + length, + testPoint, + closestPoint; + + for (var i=0; i < polygon.points.length; ++i) { + testPoint = polygon.points[i]; + length = Math.sqrt(Math.pow(testPoint.x - circle.x, 2), + Math.pow(testPoint.y - circle.y, 2)); + if (length < min) { + min = length; + closestPoint = testPoint; + } + } + + return closestPoint; +}; + +// .............................................................. +// Get the circle's axis (circle's don't have an axis, so this +// method manufactures one) +// .............................................................. + +function getCircleAxis(circle, polygon, closestPoint) { + var v1 = new Vector(new Point(circle.x, circle.y)), + v2 = new Vector(new Point(closestPoint.x, closestPoint.y)), + surfaceVector = v1.subtract(v2); + + return surfaceVector.normalize(); +}; + +// .............................................................. +// Tests to see if a polygon collides with a circle +// .............................................................. + +function polygonCollidesWithCircle (polygon, circle, displacement) { + var axes = polygon.getAxes(), + closestPoint = getPolygonPointClosestToCircle(polygon, circle); + + axes.push(getCircleAxis(circle, polygon, closestPoint)); + + return polygon.minimumTranslationVector(axes, circle, displacement); +}; + +// .............................................................. +// Given two shapes, and a set of axes, returns the minimum +// translation vector. +// .............................................................. + + +function getMTV(shape1, shape2, displacement, axes) { + var minimumOverlap = BIG_NUMBER, + overlap, + axisWithSmallestOverlap, + mtv; + + for (var i=0; i < axes.length; ++i) { + axis = axes[i]; + projection1 = shape1.project(axis); + projection2 = shape2.project(axis); + overlap = projection1.getOverlap(projection2); + + if (overlap === 0) { + return new MinimumTranslationVector(undefined, 0); + } + else { + if (overlap < minimumOverlap) { + minimumOverlap = overlap; + axisWithSmallestOverlap = axis; + } + } + } + mtv = new MinimumTranslationVector(axisWithSmallestOverlap, + minimumOverlap); + return mtv; +}; + + +// Constants..................................................... + +var BIG_NUMBER = 1000000; + + +// Points........................................................ + +var Point = function (x, y) { + this.x = x; + this.y = y; +}; + +Point.prototype = { + rotate: function (rotationPoint, angle) { + var tx, ty, rx, ry; + + tx = this.x - rotationPoint.x; // tx = translated X + ty = this.y - rotationPoint.y; // ty = translated Y + + rx = tx * Math.cos(-angle) - // rx = rotated X + ty * Math.sin(-angle); + + ry = tx * Math.sin(-angle) + // ry = rotated Y + ty * Math.cos(-angle); + + return new Point(rx + rotationPoint.x, ry + rotationPoint.y); + } +}; + +// Lines......................................................... + +var Line = function(p1, p2) { + this.p1 = p1; // point 1 + this.p2 = p2; // point 2 +} + +Line.prototype.intersectionPoint = function (line) { + var m1, m2, b1, b2, ip = new Point(); + + if (this.p1.x === this.p2.x) { + m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x); + b2 = line.p1.y - m2 * line.p1.x; + ip.x = this.p1.x; + ip.y = m2 * ip.x + b2; + } + else if(line.p1.x === line.p2.x) { + m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); + b1 = this.p1.y - m1 * this.p1.x; + ip.x = line.p1.x; + ip.y = m1 * ip.x + b1; + } + else { + m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); + m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x); + b1 = this.p1.y - m1 * this.p1.x; + b2 = line.p1.y - m2 * line.p1.x; + ip.x = (b2 - b1) / (m1 - m2); + ip.y = m1 * ip.x + b1; + } + return ip; +}; + +// Bounding boxes................................................ + +var BoundingBox = function(left, top, width, height) { + this.left = left; + this.top = top; + this.width = width; + this.height = height; +}; + + +// Vectors....................................................... + +var Vector = function(point) { + if (point === undefined) { + this.x = 0; + this.y = 0; + } + else { + this.x = point.x; + this.y = point.y; + } +}; + +Vector.prototype = { + getMagnitude: function () { + return Math.sqrt(Math.pow(this.x, 2) + + Math.pow(this.y, 2)); + }, + + setMagnitude: function (m) { + var uv = this.normalize(); + this.x = uv.x * m; + this.y = uv.y * m; + }, + + dotProduct: function (vector) { + return this.x * vector.x + + this.y * vector.y; + }, + + add: function (vector) { + var v = new Vector(); + v.x = this.x + vector.x; + v.y = this.y + vector.y; + return v; + }, + + subtract: function (vector) { + var v = new Vector(); + v.x = this.x - vector.x; + v.y = this.y - vector.y; + return v; + }, + + normalize: function () { + var v = new Vector(), + m = this.getMagnitude(); + v.x = this.x / m; + v.y = this.y / m; + return v; + }, + + perpendicular: function () { + var v = new Vector(); + v.x = this.y; + v.y = 0-this.x; + return v; + }, + + reflect: function (axis) { + var dotProductRatio, vdotl, ldotl, v = new Vector(), + vdotl = this.dotProduct(axis), + ldotl = axis.dotProduct(axis), + dotProductRatio = vdotl / ldotl; + + v.x = 2 * dotProductRatio * axis.x - this.x; + v.y = 2 * dotProductRatio * axis.y - this.y; + + return v; + } +}; + + +// Shapes........................................................ + +var Shape = function () { + this.fillStyle = 'rgba(255, 255, 0, 0.8)'; + this.strokeStyle = 'white'; +}; + +Shape.prototype = { + move: function (dx, dy) { + throw 'move(dx, dy) not implemented'; + }, + + createPath: function (context) { + throw 'createPath(context) not implemented'; + }, + + boundingBox: function () { + throw 'boundingBox() not implemented'; + }, + + fill: function (context) { + context.save(); + context.fillStyle = this.fillStyle; + this.createPath(context); + context.fill(); + context.restore(); + }, + + stroke: function (context) { + context.save(); + context.strokeStyle = this.strokeStyle; + this.createPath(context); + context.stroke(); + context.restore(); + }, + + collidesWith: function (shape, displacement) { + throw 'collidesWith(shape, displacement) not implemented'; + }, + + isPointInPath: function (context, x, y) { + this.createPath(context); + return context.isPointInPath(x, y); + }, + + project: function (axis) { + throw 'project(axis) not implemented'; + }, + + minimumTranslationVector: function (axes, shape, displacement) { + return getMTV(this, shape, displacement, axes); + } +}; + + +// Circles....................................................... + +var Circle = function (x, y, radius) { + this.x = x; + this.y = y; + this.radius = radius; + this.strokeStyle = 'blue'; + this.fillStyle = 'yellow'; +} + +Circle.prototype = new Shape(); + +Circle.prototype.centroid = function () { + return new Point(this.x,this.y); +}; + +Circle.prototype.move = function (dx, dy) { + this.x += dx; + this.y += dy; +}; + +Circle.prototype.boundingBox = function (dx, dy) { + return new BoundingBox(this.x - this.radius, + this.y - this.radius, + 2*this.radius, + 2*this.radius); +}; + +Circle.prototype.createPath = function (context) { + context.beginPath(); + context.arc(this.x, this.y, this.radius, 0, Math.PI*2, false); +}; + +Circle.prototype.project = function (axis) { + var scalars = [], + point = new Point(this.x, this.y); + dotProduct = new Vector(point).dotProduct(axis); + + scalars.push(dotProduct); + scalars.push(dotProduct + this.radius); + scalars.push(dotProduct - this.radius); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Circle.prototype.collidesWith = function (shape, displacement) { + if (shape.radius === undefined) { + return polygonCollidesWithCircle(shape, this, displacement); + } + else { + return circleCollidesWithCircle(this, shape, displacement); + } +}; + + +// Polygons...................................................... + +var Polygon = function () { + this.points = []; + this.strokeStyle = 'blue'; + this.fillStyle = 'white'; +}; + +Polygon.prototype = new Shape(); + +Polygon.prototype.getAxes = function () { + var v1, v2, surfaceVector, axes = [], pushAxis = true; + + for (var i=0; i < this.points.length-1; i++) { + v1 = new Vector(this.points[i]); + v2 = new Vector(this.points[i+1]); + + surfaceVector = v2.subtract(v1); + axes.push(surfaceVector.perpendicular().normalize()); + } + + return axes; +}; + +Polygon.prototype.project = function (axis) { + var scalars = []; + + this.points.forEach( function (point) { + scalars.push(new Vector(point).dotProduct(axis)); + }); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Polygon.prototype.addPoint = function (x, y) { + this.points.push(new Point(x,y)); +}; + +Polygon.prototype.createPath = function (context) { + if (this.points.length === 0) + return; + + context.beginPath(); + context.moveTo(this.points[0].x, + this.points[0].y); + + for (var i=0; i < this.points.length; ++i) { + context.lineTo(this.points[i].x, + this.points[i].y); + } + + context.closePath(); +}; + +Polygon.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + x += dx; + y += dy; + } +}; + +Polygon.prototype.collidesWith = function (shape, displacement) { + if (shape.radius !== undefined) { + return polygonCollidesWithCircle(this, shape, displacement); + } + else { + return polygonCollidesWithPolygon(this, shape, displacement); + } +}; + +Polygon.prototype.move = function (dx, dy) { + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +Polygon.prototype.boundingBox = function (dx, dy) { + var minx = BIG_NUMBER, + miny = BIG_NUMBER, + maxx = -BIG_NUMBER, + maxy = -BIG_NUMBER, + point; + + for (var i=0; i < this.points.length; ++i) { + point = this.points[i]; + minx = Math.min(minx,point.x); + miny = Math.min(miny,point.y); + maxx = Math.max(maxx,point.x); + maxy = Math.max(maxy,point.y); + } + + return new BoundingBox(minx, miny, + parseFloat(maxx - minx), + parseFloat(maxy - miny)); +}; + +Polygon.prototype.centroid = function () { + var pointSum = new Point(0,0); + + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + pointSum.x += point.x; + pointSum.y += point.y; + } + return new Point(pointSum.x / this.points.length, + pointSum.y / this.points.length); +} + +// Projections................................................... + +var Projection = function (min, max) { + this.min = min; + this.max = max; +}; + +Projection.prototype = { + overlaps: function (projection) { + return this.max > projection.min && projection.max > this.min; + }, + + getOverlap: function (projection) { + var overlap; + + if (!this.overlaps(projection)) + return 0; + + if (this.max > projection.max) { + overlap = projection.max - this.min; + } + else { + overlap = this.max - projection.min; + } + return overlap; + } +}; + + +// MinimumTranslationVector......................................... + +var MinimumTranslationVector = function (axis, overlap) { + this.axis = axis; + this.overlap = overlap; +}; + +var ImageShape = function(imageSource, x, y, w, h) { + var self = this; + + this.image = new Image(); + this.imageLoaded = false; + this.points = [ new Point(x,y) ]; + this.x = x; + this.y = y; + + this.image.src = imageSource; + + this.image.addEventListener('load', function (e) { + self.setPolygonPoints(); + self.imageLoaded = true; + }, false); +} + +ImageShape.prototype = new Polygon(); + +ImageShape.prototype.fill = function (context) { +}; + +ImageShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x + this.image.width, this.y)); + this.points.push(new Point(this.x + this.image.width, this.y + this.image.height)); + this.points.push(new Point(this.x, this.y + this.image.height)); + this.points.push(new Point(this.x, this.y)); + this.points.push(new Point(this.x + this.image.width, this.y)); +}; + +ImageShape.prototype.drawImage = function (context) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); +}; + +ImageShape.prototype.stroke = function (context) { + var self = this; + + if (this.imageLoaded) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); + } + else { + this.image.addEventListener('load', function (e) { + self.drawImage(context); + }, false); + } +}; + + +var SpriteShape = function (sprite, x, y) { + this.sprite = sprite; + this.x = x; + this.y = y; + sprite.left = x; + sprite.top = y; + this.setPolygonPoints(); +}; + +SpriteShape.prototype = new Polygon(); + +SpriteShape.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } + this.sprite.left = this.points[0].x; + this.sprite.top = this.points[0].y; +}; + +SpriteShape.prototype.fill = function (context) { +}; + +SpriteShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x, this.y)); + this.points.push(new Point(this.x, this.y + this.sprite.height)); + this.points.push(new Point(this.x + this.sprite.width, this.y + this.sprite.height)); + this.points.push(new Point(this.x + this.sprite.width, this.y)); + this.points.push(new Point(this.x, this.y)); +}; +/* +SpriteShape.prototype.stroke = function (context) { + this.sprite.paint(context); +}; +*/ + diff --git a/canvas/ch08/example-8.2/example.html b/canvas/ch08/example-8.2/example.html new file mode 100644 index 0000000..676e2d9 --- /dev/null +++ b/canvas/ch08/example-8.2/example.html @@ -0,0 +1,95 @@ + + + + + + Bucket + + + + + + + Canvas not supported + + +
0
+ +
+ Launch velocity (m/s): m/s
+ Launch angle (degrees): degrees
+
+ + + + + + diff --git a/canvas/ch08/example-8.2/example.js b/canvas/ch08/example-8.2/example.js new file mode 100644 index 0000000..27dfa63 --- /dev/null +++ b/canvas/ch08/example-8.2/example.js @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + scoreboard = document.getElementById('scoreboard'), + launchVelocityOutput = document.getElementById('launchVelocityOutput'), + launchAngleOutput = document.getElementById('launchAngleOutput'), + + elapsedTime = undefined, + launchTime = undefined, + + score = 0, + lastScore = 0, + lastMouse = { left: 0, top: 0 }, + + threePointer = false, + needInstructions = true, + + LAUNCHPAD_X = 50, + LAUNCHPAD_Y = context.canvas.height-50, + LAUNCHPAD_WIDTH = 50, + LAUNCHPAD_HEIGHT = 12, + BALL_RADIUS = 8, + ARENA_LENGTH_IN_METERS = 10, + INITIAL_LAUNCH_ANGLE = Math.PI/4, + + launchAngle = INITIAL_LAUNCH_ANGLE, + pixelsPerMeter = canvas.width / ARENA_LENGTH_IN_METERS, + + // LaunchPad................................................. + + launchPadPainter = { + LAUNCHPAD_FILL_STYLE: 'rgb(100,140,230)', + + paint: function (ledge, context) { + context.save(); + context.fillStyle = this.LAUNCHPAD_FILL_STYLE; + context.fillRect(LAUNCHPAD_X, LAUNCHPAD_Y, + LAUNCHPAD_WIDTH, LAUNCHPAD_HEIGHT); + context.restore(); + } + }, + + launchPad = new Sprite('launchPad', launchPadPainter), + + // Ball...................................................... + + ballPainter = { + BALL_FILL_STYLE: 'rgb(255,255,0)', + BALL_STROKE_STYLE: 'rgb(0,0,0,0.4)', + + paint: function (ball, context) { + context.save(); + context.shadowColor = undefined; + context.lineWidth = 2; + context.fillStyle = this.BALL_FILL_STYLE; + context.strokeStyle = this.BALL_STROKE_STYLE; + + context.beginPath(); + context.arc(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS, + ball.radius, 0, Math.PI*2, false); + + context.clip(); + context.fill(); + context.stroke(); + context.restore(); + } + }, + + // Lob behavior.............................................. + + lob = { + lastTime: 0, + GRAVITY_FORCE: 9.81, // m/s/s + + applyGravity: function (elapsed) { + ball.velocityY = (this.GRAVITY_FORCE * elapsed) - + (launchVelocity * Math.sin(launchAngle)); + }, + + updateBallPosition: function (updateDelta) { + ball.left += ball.velocityX * (updateDelta) * pixelsPerMeter; + ball.top += ball.velocityY * (updateDelta) * pixelsPerMeter; + }, + + checkForThreePointer: function () { + if (ball.top < 0) { + threePointer = true; + } + }, + + checkBallBounds: function () { + if (ball.top > canvas.height || ball.left > canvas.width) { + reset(); + } + }, + + execute: function (ball, context, time) { + var updateDelta, + elapsedFlightTime; + + if (ballInFlight) { + elapsedFrameTime = (time - this.lastTime)/1000, + elapsedFlightTime = (time - launchTime)/1000; + + this.applyGravity(elapsedFlightTime); + this.updateBallPosition(elapsedFrameTime); + this.checkForThreePointer(); + this.checkBallBounds(); + } + this.lastTime = time; + } + }, + + ball = new Sprite('ball', ballPainter, [ lob ]), + ballInFlight = false, + + // Bucket.................................................... + + BUCKET_LEFT = 668, + BUCKET_TOP = canvas.height - 100, + BUCKET_WIDTH = 83, + BUCKET_HEIGHT = 62, + bucketHitCenter = { x: BUCKET_LEFT + 2*this.BUCKET_WIDTH/3, + y: BUCKET_TOP + BUCKET_HEIGHT/8 + }, + + bucketHitRadius = BUCKET_WIDTH/8, + bucketImage = new Image(), + + catchBall = { + isBallInBucket: function() { + var ballCenter = { x: ball.left + BALL_RADIUS, + y: ball.top + BALL_RADIUS + }, + + distance = Math.sqrt( + Math.pow(bucketHitCenter.x - ballCenter.x, 2) + + Math.pow(bucketHitCenter.y - ballCenter.y, 2)); + + return distance < BALL_RADIUS + bucketHitRadius; + }, + + adjustScore: function() { + if (threePointer) lastScore = 3; + else lastScore = 2; + + score += lastScore; + scoreboard.innerHTML = score; + }, + + execute: function (bucket, context, time) { + if (ballInFlight && this.isBallInBucket()) { + reset(); + //freeze(); + this.adjustScore(); + } + } + }, + + bucket = new Sprite('bucket', { + + paint: function (sprite, context) { + context.drawImage(bucketImage, BUCKET_LEFT, BUCKET_TOP); + context.fillStyle = 'white'; + context.beginPath(); + context.arc(bucketHitCenter.x, bucketHitCenter.y, + bucketHitRadius, 0, Math.PI*2, false); + context.fill(); + } + }, + + [ catchBall ] + ); + +// Functions..................................................... + +function freeze() { + ball.velocityX = 0; + ball.velocityY = 0; + ballInFlight = false; + needInstructions = false; +} + +function reset() { + ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2 - BALL_RADIUS; + ball.top = LAUNCHPAD_Y - ball.height/2 - BALL_RADIUS; + ball.velocityX = 0; + ball.velocityY = 0; + ballInFlight = false; + needInstructions = false; + lastScore = 0; +} + +function showText(text) { + var metrics; + + metrics = context.measureText(text); + + context.save(); + context.shadowColor = undefined; + context.strokeStyle = 'navy'; + context.fillStyle = 'goldenrod'; + + context.fillText(text, + canvas.width/2 - metrics.width/2, + canvas.height/2); + + context.strokeText(text, + canvas.width/2 - metrics.width/2, + canvas.height/2); + context.restore(); +} + +function drawRubberband() { + context.beginPath(); + context.moveTo(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS); + context.lineTo(bucketHitCenter.x, bucketHitCenter.y); + context.stroke(); +}; + +function drawGuidewire() { + context.moveTo(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS); + context.lineTo(lastMouse.left, lastMouse.top); + context.stroke(); +}; + +function updateBackgroundText() { + if (lastScore == 3) showText('Three pointer!'); + else if (lastScore == 2) showText('Nice shot!'); + else if (needInstructions) showText('Click to launch ball'); +}; + +function resetScoreLater() { + setTimeout(function () { + lastScore = 0; + }, 3000); +}; + +function updateSprites(time) { + bucket.update(context, time); + launchPad.update(context, time); + ball.update(context, time); +} + +function paintSprites() { + launchPad.paint(context); + bucket.paint(context); + ball.paint(context); +} + +function mouseToCanvas(e) { + var rect = canvas.getBoundingClientRect(), + loc = { x: e.x || e.clientX, + y: e.y || e.clientY + }; + + loc.x -= rect.left; + loc.y -= rect.top; + + return loc; +} + +// Event handlers................................................ + +canvas.onmousedown = function(e) { + var rect; + + e.preventDefault(); + + if ( ! ballInFlight) { + ball.velocityX = launchVelocity * Math.cos(launchAngle); + ball.velocityY = launchVelocity * Math.sin(launchAngle); + ballInFlight = true; + threePointer = false; + launchTime = +new Date(); + } +}; + +canvas.onmousemove = function (e) { + var rect; + + e.preventDefault(); + + if ( ! ballInFlight) { + loc = mouseToCanvas(e); + lastMouse.left = loc.x; + lastMouse.top = loc.y; + + deltaX = Math.abs(lastMouse.left - ball.left); + deltaY = Math.abs(lastMouse.top - ball.top); + + launchAngle = Math.atan(parseFloat(deltaY) / parseFloat(deltaX)); + launchVelocity = 4 * deltaY / Math.sin(launchAngle) / pixelsPerMeter; + + launchVelocityOutput.innerHTML = launchVelocity.toFixed(2); + launchAngleOutput.innerHTML = (launchAngle * 180/Math.PI).toFixed(2); + } +}; + +// Animation Loop................................................ + +function animate(time) { + elapsedTime = (time - launchTime) / 1000; + context.clearRect(0,0,canvas.width,canvas.height); + + if (!ballInFlight) { + drawGuidewire(); + updateBackgroundText(); + + if (lastScore !== 0) { // just scored + resetScoreLater(); + } + } + + updateSprites(time); + paintSprites(); + + drawRubberband(); + + window.requestNextAnimationFrame(animate); +} + +// Initialization................................................ + +ball.width = BALL_RADIUS*2; +ball.height = ball.width; +ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2 - BALL_RADIUS; +ball.top = LAUNCHPAD_Y - ball.height/2 - BALL_RADIUS; +ball.radius = BALL_RADIUS; + +context.lineWidth = 0.5; +context.strokeStyle = 'rgba(0,0,0,0.5)'; +context.shadowColor = 'rgba(0,0,0,0.5)'; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; +context.stroke(); + +context.font = '72px fantasy'; + +bucketImage.src = '../../shared/images/bucket.png'; +bucketImage.onload = function (e) { + bucket.left = BUCKET_LEFT; + bucket.top = BUCKET_TOP; + bucket.width = BUCKET_WIDTH; + bucket.height = BUCKET_HEIGHT; +}; + +animate(+new Date()); diff --git a/canvas/ch08/example-8.20/example.html b/canvas/ch08/example-8.20/example.html new file mode 100644 index 0000000..2c65ede --- /dev/null +++ b/canvas/ch08/example-8.20/example.html @@ -0,0 +1,60 @@ + + + + + + Bouncing using SAT and MTV + + + + + + + Canvas not supported + + + + + + + diff --git a/canvas/ch08/example-8.20/example.js b/canvas/ch08/example-8.20/example.js new file mode 100644 index 0000000..88aa34b --- /dev/null +++ b/canvas/ch08/example-8.20/example.js @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + shapes = [], + polygonPoints = [ + [ new Point(250, 150), new Point(250, 200), + new Point(300, 200) ], + + [ new Point(150, 100), new Point(150, 150), + new Point(200, 150) ], + + [ new Point(150, 250), new Point(150, 200), + new Point(200, 200) ], + + [ new Point(100, 75), new Point(100, 100), + new Point(125, 100), new Point(125, 75) ], + + [ new Point(300, 75), new Point(280, 125), + new Point(350, 125) ] + ], + + polygonStrokeStyles = [ 'blue', 'yellow', 'red', 'red', 'black'], + polygonFillStyles = [ 'rgba(255,255,0,0.7)', + 'rgba(100,140,230,0.6)', + 'rgba(255,255,255,0.6)', + 'aqua', + 'rgba(255,0,255,0.8)' ], + shapeMoving = undefined, + c1 = new Circle(150, 275, 10), + c2 = new Circle(350, 200, 15), + + lastTime = undefined, + velocity = new Vector(new Point(350, 190)), + lastVelocity = { x: 350, y: 190 }, + showInstructions = true; + +// Functions..................................................... + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function drawShapes() { + shapes.forEach( function (shape) { + shape.stroke(context); + shape.fill(context); + }); +} + +function separate(mtv) { + var dx, dy, velocityMagnitude, point; + + if (mtv.axis === undefined) { + point = new Point(); + velocityMagnitude = Math.sqrt(Math.pow(velocity.x, 2) + + Math.pow(velocity.y, 2)); + + point.x = velocity.x / velocityMagnitude; + point.y = velocity.y / velocityMagnitude; + + mtv.axis = new Vector(point); + } + + dy = mtv.axis.y * mtv.overlap; + dx = mtv.axis.x * mtv.overlap + + if ((dx < 0 && velocity.x < 0) || + (dx > 0 && velocity.x > 0)) { + dx = -dx; + } + + if ((dy < 0 && velocity.y < 0) || + (dy > 0 && velocity.y > 0)) { + dy = -dy; + } + + shapeMoving.move(dx, dy); +} + +function checkMTVAxisDirection(mtv, collider, collidee) { + var centroid1, centroid2, centroidVector, centroidUnitVector; + + if (mtv.axis === undefined) + return; + + centroid1 = new Vector(collider.centroid()), + centroid2 = new Vector(collidee.centroid()), + centroidVector = centroid2.subtract(centroid1), + centroidUnitVector = (new Vector(centroidVector)).normalize(); + + if (centroidUnitVector.dotProduct(mtv.axis) > 0) { + mtv.axis.x = -mtv.axis.x; + mtv.axis.y = -mtv.axis.y; + } + +}; + +function bounce(mtv, collider, collidee) { + var dotProductRatio, vdotl, ldotl, point, + velocityVector = new Vector(new Point(velocity.x, velocity.y)), + velocityUnitVector = velocityVector.normalize(), + velocityVectorMagnitude = velocityVector.getMagnitude(), + perpendicular; + + if (shapeMoving) { + checkMTVAxisDirection(mtv, collider, collidee) + + point = new Point(); + + if (mtv.axis !== undefined) { + perpendicular = mtv.axis.perpendicular(); + } + else { + perpendicular = new Vector(new Point(-velocityUnitVector.y, + velocityUnitVector.x)); + } + + vdotl = velocityUnitVector.dotProduct(perpendicular); + ldotl = perpendicular.dotProduct(perpendicular); + dotProductRatio = vdotl / ldotl; + + point.x = 2 * dotProductRatio * perpendicular.x - velocityUnitVector.x; + point.y = 2 * dotProductRatio * perpendicular.y - velocityUnitVector.y; + + separate(mtv); + + velocity.x = point.x * velocityVectorMagnitude; + velocity.y = point.y * velocityVectorMagnitude; + } +} + +function collisionDetected(mtv) { + return mtv.axis != undefined || mtv.overlap !== 0; +}; + +function handleEdgeCollisions() { + var bbox = shapeMoving.boundingBox(), + right = bbox.left + bbox.width, + bottom = bbox.top + bbox.height; + + if (right > canvas.width || bbox.left < 0) { + velocity.x = -velocity.x; + + if (right > canvas.width) + shapeMoving.move(0-(right-canvas.width), 0); + + if (bbox.left < 0) + shapeMoving.move(-bbox.left, 0); + } + if (bottom > canvas.height || bbox.top < 0) { + velocity.y = -velocity.y; + + if (bottom > canvas.height) + shapeMoving.move(0, 0-(bottom-canvas.height)); + if (bbox.top < 0) + shapeMoving.move(0, -bbox.top); + } +}; + +function handleShapeCollisions() { + var mtv; + + shapes.forEach( function (shape) { + if (shape !== shapeMoving) { + mtv = shapeMoving.collidesWith(shape); + if (collisionDetected(mtv)) { + bounce(mtv, shapeMoving, shape); + } + } + }); +}; + +function detectCollisions() { + if (shapeMoving) { + handleShapeCollisions(); + handleEdgeCollisions(); + } +}; + + +// Event Handlers................................................ + +canvas.onmousedown = function (e) { + var location = windowToCanvas(e); + + if (showInstructions) + showInstructions = false; + + velocity.x = lastVelocity.x; + velocity.y = lastVelocity.y; + + shapeMoving = undefined; + + shapes.forEach( function (shape) { + if (shape.isPointInPath(context, location.x, location.y)) { + shapeMoving = shape; + } + }); +}; + +canvas.onmouseup = function (e) { + lastVelocity.x = velocity.x; + lastVelocity.y = velocity.y; +}; + +// Animation..................................................... + +function animate(time) { + var elapsedTime; + + if (lastTime === 0) { + lastTime = time; + } + else { + context.clearRect(0,0,canvas.width,canvas.height); + drawGrid('lightgray', 10, 10); + + if (shapeMoving !== undefined) { + elapsedTime = parseFloat(time - lastTime) / 1000; + shapeMoving.move(velocity.x * elapsedTime, + velocity.y * elapsedTime); + + detectCollisions(); + } + + drawShapes(); + lastTime = time; + + if (showInstructions) { + context.fillStyle = 'cornflowerblue'; + context.font = '24px Arial'; + context.fillText('Click on a shape to animate it', 20, 40); + context.fillText('Click on the background to stop animating', 20, 65); + } + } + window.requestNextAnimationFrame(animate); +}; + +function drawGrid(color, stepx, stepy) { + context.save() + + context.shadowColor = undefined; + context.shadowBlur = 0;; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + + context.beginPath(); + + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + } + context.stroke(); + + context.beginPath(); + + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + } + context.stroke(); + + context.restore(); +} + +// Initialization................................................ + +for (var i=0; i < polygonPoints.length; ++i) { + var polygon = new Polygon(), + points = polygonPoints[i]; + + polygon.strokeStyle = polygonStrokeStyles[i]; + polygon.fillStyle = polygonFillStyles[i]; + + points.forEach( function (point) { + polygon.addPoint(point.x, point.y); + }); + + shapes.push(polygon); +} + +c1.fillStyle = 'rgba(255,255,0,1.0)'; +c2.strokeStyle = 'rgba(255,255,0,1.0)'; +c2.fillStyle = 'rgba(0,0,255,0.6)'; +shapes.push(c1); +shapes.push(c2); + +if (navigator.userAgent.indexOf('Opera') === -1) + context.shadowColor = 'rgba(100,140,255,0.5)'; + +context.shadowBlur = 4; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.font = '38px Arial'; + +drawGrid('lightgray', 10, 10); +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch08/example-8.20/shapes.js b/canvas/ch08/example-8.20/shapes.js new file mode 100644 index 0000000..cf704eb --- /dev/null +++ b/canvas/ch08/example-8.20/shapes.js @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Functions..................................................... + +// .............................................................. +// Check to see if a polygon collides with another polygon +// .............................................................. + +function polygonCollidesWithPolygon (p1, p2, displacement) { // displacement for p1 + var mtv1 = p1.minimumTranslationVector(p1.getAxes(), p2, displacement), + mtv2 = p1.minimumTranslationVector(p2.getAxes(), p2, displacement); + + if (mtv1.overlap === 0 || mtv2.overlap === 0) + return { axis: undefined, overlap: 0 }; + else + return mtv1.overlap < mtv2.overlap ? mtv1 : mtv2; +}; + +// .............................................................. +// Check to see if a circle collides with another circle +// .............................................................. + +function circleCollidesWithCircle (c1, c2) { + var distance = Math.sqrt( Math.pow(c2.x - c1.x, 2) + + Math.pow(c2.y - c1.y, 2)), + overlap = Math.abs(c1.radius + c2.radius) - distance; + + return overlap < 0 ? + new MinimumTranslationVector(undefined, 0) : + new MinimumTranslationVector(undefined, overlap); +}; + +// .............................................................. +// Get the polygon's point that's closest to the circle +// .............................................................. + +function getPolygonPointClosestToCircle(polygon, circle) { + var min = BIG_NUMBER, + length, + testPoint, + closestPoint; + + for (var i=0; i < polygon.points.length; ++i) { + testPoint = polygon.points[i]; + length = Math.sqrt(Math.pow(testPoint.x - circle.x, 2), + Math.pow(testPoint.y - circle.y, 2)); + if (length < min) { + min = length; + closestPoint = testPoint; + } + } + + return closestPoint; +}; + +// .............................................................. +// Get the circle's axis (circle's don't have an axis, so this +// method manufactures one) +// .............................................................. + +function getCircleAxis(circle, polygon, closestPoint) { + var v1 = new Vector(new Point(circle.x, circle.y)), + v2 = new Vector(new Point(closestPoint.x, closestPoint.y)), + surfaceVector = v1.subtract(v2); + + return surfaceVector.normalize(); +}; + +// .............................................................. +// Tests to see if a polygon collides with a circle +// .............................................................. + +function polygonCollidesWithCircle (polygon, circle, displacement) { + var axes = polygon.getAxes(), + closestPoint = getPolygonPointClosestToCircle(polygon, circle); + + axes.push(getCircleAxis(circle, polygon, closestPoint)); + + return polygon.minimumTranslationVector(axes, circle, displacement); +}; + +// .............................................................. +// Given two shapes, and a set of axes, returns the minimum +// translation vector. +// .............................................................. + + +function getMTV(shape1, shape2, displacement, axes) { + var minimumOverlap = BIG_NUMBER, + overlap, + axisWithSmallestOverlap, + mtv; + + for (var i=0; i < axes.length; ++i) { + axis = axes[i]; + projection1 = shape1.project(axis); + projection2 = shape2.project(axis); + overlap = projection1.getOverlap(projection2); + + if (overlap === 0) { + return new MinimumTranslationVector(undefined, 0); + } + else { + if (overlap < minimumOverlap) { + minimumOverlap = overlap; + axisWithSmallestOverlap = axis; + } + } + } + mtv = new MinimumTranslationVector(axisWithSmallestOverlap, + minimumOverlap); + return mtv; +}; + + +// Constants..................................................... + +var BIG_NUMBER = 1000000; + + +// Points........................................................ + +var Point = function (x, y) { + this.x = x; + this.y = y; +}; + +Point.prototype = { + rotate: function (rotationPoint, angle) { + var tx, ty, rx, ry; + + tx = this.x - rotationPoint.x; // tx = translated X + ty = this.y - rotationPoint.y; // ty = translated Y + + rx = tx * Math.cos(-angle) - // rx = rotated X + ty * Math.sin(-angle); + + ry = tx * Math.sin(-angle) + // ry = rotated Y + ty * Math.cos(-angle); + + return new Point(rx + rotationPoint.x, ry + rotationPoint.y); + } +}; + +// Lines......................................................... + +var Line = function(p1, p2) { + this.p1 = p1; // point 1 + this.p2 = p2; // point 2 +} + +Line.prototype.intersectionPoint = function (line) { + var m1, m2, b1, b2, ip = new Point(); + + if (this.p1.x === this.p2.x) { + m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x); + b2 = line.p1.y - m2 * line.p1.x; + ip.x = this.p1.x; + ip.y = m2 * ip.x + b2; + } + else if(line.p1.x === line.p2.x) { + m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); + b1 = this.p1.y - m1 * this.p1.x; + ip.x = line.p1.x; + ip.y = m1 * ip.x + b1; + } + else { + m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); + m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x); + b1 = this.p1.y - m1 * this.p1.x; + b2 = line.p1.y - m2 * line.p1.x; + ip.x = (b2 - b1) / (m1 - m2); + ip.y = m1 * ip.x + b1; + } + return ip; +}; + +// Bounding boxes................................................ + +var BoundingBox = function(left, top, width, height) { + this.left = left; + this.top = top; + this.width = width; + this.height = height; +}; + + +// Vectors....................................................... + +var Vector = function(point) { + if (point === undefined) { + this.x = 0; + this.y = 0; + } + else { + this.x = point.x; + this.y = point.y; + } +}; + +Vector.prototype = { + getMagnitude: function () { + return Math.sqrt(Math.pow(this.x, 2) + + Math.pow(this.y, 2)); + }, + + setMagnitude: function (m) { + var uv = this.normalize(); + this.x = uv.x * m; + this.y = uv.y * m; + }, + + dotProduct: function (vector) { + return this.x * vector.x + + this.y * vector.y; + }, + + add: function (vector) { + var v = new Vector(); + v.x = this.x + vector.x; + v.y = this.y + vector.y; + return v; + }, + + subtract: function (vector) { + var v = new Vector(); + v.x = this.x - vector.x; + v.y = this.y - vector.y; + return v; + }, + + normalize: function () { + var v = new Vector(), + m = this.getMagnitude(); + v.x = this.x / m; + v.y = this.y / m; + return v; + }, + + perpendicular: function () { + var v = new Vector(); + v.x = this.y; + v.y = 0-this.x; + return v; + }, + + reflect: function (axis) { + var dotProductRatio, vdotl, ldotl, v = new Vector(), + vdotl = this.dotProduct(axis), + ldotl = axis.dotProduct(axis), + dotProductRatio = vdotl / ldotl; + + v.x = 2 * dotProductRatio * axis.x - this.x; + v.y = 2 * dotProductRatio * axis.y - this.y; + + return v; + } +}; + + +// Shapes........................................................ + +var Shape = function () { + this.fillStyle = 'rgba(255, 255, 0, 0.8)'; + this.strokeStyle = 'white'; +}; + +Shape.prototype = { + move: function (dx, dy) { + throw 'move(dx, dy) not implemented'; + }, + + createPath: function (context) { + throw 'createPath(context) not implemented'; + }, + + boundingBox: function () { + throw 'boundingBox() not implemented'; + }, + + fill: function (context) { + context.save(); + context.fillStyle = this.fillStyle; + this.createPath(context); + context.fill(); + context.restore(); + }, + + stroke: function (context) { + context.save(); + context.strokeStyle = this.strokeStyle; + this.createPath(context); + context.stroke(); + context.restore(); + }, + + collidesWith: function (shape, displacement) { + throw 'collidesWith(shape, displacement) not implemented'; + }, + + isPointInPath: function (context, x, y) { + this.createPath(context); + return context.isPointInPath(x, y); + }, + + project: function (axis) { + throw 'project(axis) not implemented'; + }, + + minimumTranslationVector: function (axes, shape, displacement) { + return getMTV(this, shape, displacement, axes); + } +}; + + +// Circles....................................................... + +var Circle = function (x, y, radius) { + this.x = x; + this.y = y; + this.radius = radius; + this.strokeStyle = 'blue'; + this.fillStyle = 'yellow'; +} + +Circle.prototype = new Shape(); + +Circle.prototype.centroid = function () { + return new Point(this.x,this.y); +}; + +Circle.prototype.move = function (dx, dy) { + this.x += dx; + this.y += dy; +}; + +Circle.prototype.boundingBox = function (dx, dy) { + return new BoundingBox(this.x - this.radius, + this.y - this.radius, + 2*this.radius, + 2*this.radius); +}; + +Circle.prototype.createPath = function (context) { + context.beginPath(); + context.arc(this.x, this.y, this.radius, 0, Math.PI*2, false); +}; + +Circle.prototype.project = function (axis) { + var scalars = [], + point = new Point(this.x, this.y); + dotProduct = new Vector(point).dotProduct(axis); + + scalars.push(dotProduct); + scalars.push(dotProduct + this.radius); + scalars.push(dotProduct - this.radius); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Circle.prototype.collidesWith = function (shape, displacement) { + if (shape.radius === undefined) { + return polygonCollidesWithCircle(shape, this, displacement); + } + else { + return circleCollidesWithCircle(this, shape, displacement); + } +}; + + +// Polygons...................................................... + +var Polygon = function () { + this.points = []; + this.strokeStyle = 'blue'; + this.fillStyle = 'white'; +}; + +Polygon.prototype = new Shape(); + +Polygon.prototype.getAxes = function () { + var v1, v2, surfaceVector, axes = [], pushAxis = true; + + for (var i=0; i < this.points.length-1; i++) { + v1 = new Vector(this.points[i]); + v2 = new Vector(this.points[i+1]); + + surfaceVector = v2.subtract(v1); + axes.push(surfaceVector.perpendicular().normalize()); + } + + return axes; +}; + +Polygon.prototype.project = function (axis) { + var scalars = []; + + this.points.forEach( function (point) { + scalars.push(new Vector(point).dotProduct(axis)); + }); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Polygon.prototype.addPoint = function (x, y) { + this.points.push(new Point(x,y)); +}; + +Polygon.prototype.createPath = function (context) { + if (this.points.length === 0) + return; + + context.beginPath(); + context.moveTo(this.points[0].x, + this.points[0].y); + + for (var i=0; i < this.points.length; ++i) { + context.lineTo(this.points[i].x, + this.points[i].y); + } + + context.closePath(); +}; + +Polygon.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + x += dx; + y += dy; + } +}; + +Polygon.prototype.collidesWith = function (shape, displacement) { + if (shape.radius !== undefined) { + return polygonCollidesWithCircle(this, shape, displacement); + } + else { + return polygonCollidesWithPolygon(this, shape, displacement); + } +}; + +Polygon.prototype.move = function (dx, dy) { + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +Polygon.prototype.boundingBox = function (dx, dy) { + var minx = BIG_NUMBER, + miny = BIG_NUMBER, + maxx = -BIG_NUMBER, + maxy = -BIG_NUMBER, + point; + + for (var i=0; i < this.points.length; ++i) { + point = this.points[i]; + minx = Math.min(minx,point.x); + miny = Math.min(miny,point.y); + maxx = Math.max(maxx,point.x); + maxy = Math.max(maxy,point.y); + } + + return new BoundingBox(minx, miny, + parseFloat(maxx - minx), + parseFloat(maxy - miny)); +}; + +Polygon.prototype.centroid = function () { + var pointSum = new Point(0,0); + + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + pointSum.x += point.x; + pointSum.y += point.y; + } + return new Point(pointSum.x / this.points.length, + pointSum.y / this.points.length); +} + +// Projections................................................... + +var Projection = function (min, max) { + this.min = min; + this.max = max; +}; + +Projection.prototype = { + overlaps: function (projection) { + return this.max > projection.min && projection.max > this.min; + }, + + getOverlap: function (projection) { + var overlap; + + if (!this.overlaps(projection)) + return 0; + + if (this.max > projection.max) { + overlap = projection.max - this.min; + } + else { + overlap = this.max - projection.min; + } + return overlap; + } +}; + + +// MinimumTranslationVector......................................... + +var MinimumTranslationVector = function (axis, overlap) { + this.axis = axis; + this.overlap = overlap; +}; + +var ImageShape = function(imageSource, x, y, w, h) { + var self = this; + + this.image = new Image(); + this.imageLoaded = false; + this.points = [ new Point(x,y) ]; + this.x = x; + this.y = y; + + this.image.src = imageSource; + + this.image.addEventListener('load', function (e) { + self.setPolygonPoints(); + self.imageLoaded = true; + }, false); +} + +ImageShape.prototype = new Polygon(); + +ImageShape.prototype.fill = function (context) { +}; + +ImageShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x + this.image.width, this.y)); + this.points.push(new Point(this.x + this.image.width, this.y + this.image.height)); + this.points.push(new Point(this.x, this.y + this.image.height)); + this.points.push(new Point(this.x, this.y)); + this.points.push(new Point(this.x + this.image.width, this.y)); +}; + +ImageShape.prototype.drawImage = function (context) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); +}; + +ImageShape.prototype.stroke = function (context) { + var self = this; + + if (this.imageLoaded) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); + } + else { + this.image.addEventListener('load', function (e) { + self.drawImage(context); + }, false); + } +}; + + +var SpriteShape = function (sprite, x, y) { + this.sprite = sprite; + this.x = x; + this.y = y; + sprite.left = x; + sprite.top = y; + this.setPolygonPoints(); +}; + +SpriteShape.prototype = new Polygon(); + +SpriteShape.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } + this.sprite.left = this.points[0].x; + this.sprite.top = this.points[0].y; +}; + +SpriteShape.prototype.fill = function (context) { +}; + +SpriteShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x, this.y)); + this.points.push(new Point(this.x, this.y + this.sprite.height)); + this.points.push(new Point(this.x + this.sprite.width, this.y + this.sprite.height)); + this.points.push(new Point(this.x + this.sprite.width, this.y)); + this.points.push(new Point(this.x, this.y)); +}; +/* +SpriteShape.prototype.stroke = function (context) { + this.sprite.paint(context); +}; +*/ + diff --git a/canvas/ch08/example-8.8/example.html b/canvas/ch08/example-8.8/example.html new file mode 100644 index 0000000..c77b88e --- /dev/null +++ b/canvas/ch08/example-8.8/example.html @@ -0,0 +1,56 @@ + + + + + + Polygon Collision Detection using SAT + + + + + + + Canvas not supported + + + + + + diff --git a/canvas/ch08/example-8.8/example.js b/canvas/ch08/example-8.8/example.js new file mode 100644 index 0000000..76ac5b9 --- /dev/null +++ b/canvas/ch08/example-8.8/example.js @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + shapes = [], + polygonPoints = [ + // The paths described by these point arrays + // are open. They are explicitly closed by + // Polygon.createPath() and Polygon.getAxes() + + [ new Point(250, 150), new Point(250, 250), + new Point(350, 250) ], + + [ new Point(100, 100), new Point(100, 150), + new Point(150, 150), new Point(150, 100) ], + + [ new Point(400, 100), new Point(380, 150), + new Point(500, 150), new Point(520, 100) ] + ], + + polygonStrokeStyles = [ 'blue', 'yellow', 'red'], + polygonFillStyles = [ 'rgba(255,255,0,0.7)', + 'rgba(100,140,230,0.6)', + 'rgba(255,255,255,0.8)' ], + + mousedown = { x: 0, y: 0 }, + lastdrag = { x: 0, y: 0 }, + shapeBeingDragged = undefined; + +// Functions..................................................... + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function drawShapes() { + shapes.forEach( function (shape) { + shape.stroke(context); + shape.fill(context); + }); +} + +function detectCollisions() { + var textY = 30, + numShapes = shapes.length, + shape, + i; + + if (shapeBeingDragged) { + for(i = 0; i < numShapes; ++i) { + shape = shapes[i]; + + if (shape !== shapeBeingDragged) { + if (shapeBeingDragged.collidesWith(shape)) { + context.fillStyle = shape.fillStyle; + context.fillText('collision', 20, textY); + textY += 40; + } + } + } + } +} +// Event handlers................................................ + +canvas.onmousedown = function (e) { + var location = windowToCanvas(e); + + shapes.forEach( function (shape) { + if (shape.isPointInPath(context, location.x, location.y)) { + shapeBeingDragged = shape; + mousedown.x = location.x; + mousedown.y = location.y; + lastdrag.x = location.x; + lastdrag.y = location.y; + } + }); +} + +canvas.onmousemove = function (e) { + var location, + dragVector; + + if (shapeBeingDragged !== undefined) { + location = windowToCanvas(e); + dragVector = { x: location.x - lastdrag.x, + y: location.y - lastdrag.y + }; + + shapeBeingDragged.move(dragVector.x, dragVector.y); + + lastdrag.x = location.x; + lastdrag.y = location.y; + + context.clearRect(0,0,canvas.width,canvas.height); + drawShapes(); + detectCollisions(); + } +} + +canvas.onmouseup = function (e) { + shapeBeingDragged = undefined; +} + +for (var i=0; i < polygonPoints.length; ++i) { + var polygon = new Polygon(), + points = polygonPoints[i]; + + polygon.strokeStyle = polygonStrokeStyles[i]; + polygon.fillStyle = polygonFillStyles[i]; + + points.forEach( function (point) { + polygon.addPoint(point.x, point.y); + }); + + shapes.push(polygon); +} + +// Initialization................................................ + +context.shadowColor = 'rgba(100,140,255,0.5)'; +context.shadowBlur = 4; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.font = '38px Arial'; + +drawShapes(); + +context.save(); +context.fillStyle = 'cornflowerblue'; +context.font = '24px Arial'; +context.fillText('Drag shapes over each other', 10, 25); +context.restore(); diff --git a/canvas/ch08/section-8.1.1/animationTimer.js b/canvas/ch08/section-8.1.1/animationTimer.js new file mode 100644 index 0000000..866302b --- /dev/null +++ b/canvas/ch08/section-8.1.1/animationTimer.js @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// AnimationTimer.................................................................. +// +// An animation runs for a duration, in milliseconds. It's up to you, +// however, to start and stop the animation -- animations do not stop +// automatically. You can check to see if an animation is over with the +// isOver() method, and you can see if an animation is running with +// isRunning(). Note that animations can be over, but still running. +// +// You can also supply an optional timeWarp function that warps the percent +// completed for the animation. That warping lets you do easily incorporate +// non-linear motion, such as: ease-in, ease-out, elastic, etc. + +AnimationTimer = function (duration, timeWarp) { + this.timeWarp = timeWarp; + + if (duration !== undefined) this.duration = duration; + else this.duration = 1000; + + this.stopwatch = new Stopwatch(); +}; + +AnimationTimer.prototype = { + start: function () { + this.stopwatch.start(); + }, + + stop: function () { + this.stopwatch.stop(); + }, + + getRealElapsedTime: function () { + return this.stopwatch.getElapsedTime(); + }, + + getElapsedTime: function () { + var elapsedTime = this.stopwatch.getElapsedTime(), + percentComplete = elapsedTime / this.duration; + + if (!this.stopwatch.running) return undefined; + if (this.timeWarp == undefined) return elapsedTime; + + return elapsedTime * (this.timeWarp(percentComplete) / percentComplete); + }, + + isRunning: function() { + return this.stopwatch.running; + }, + + isOver: function () { + return this.stopwatch.getElapsedTime() > this.duration; + }, + + reset: function() { + this.stopwatch.reset(); + } +}; + +AnimationTimer.makeEaseOut = function (strength) { + return function (percentComplete) { + return 1 - Math.pow(1 - percentComplete, strength*2); + }; +}; + +AnimationTimer.makeEaseIn = function (strength) { + return function (percentComplete) { + return Math.pow(percentComplete, strength*2); + }; +}; + +AnimationTimer.makeEaseInOut = function () { + return function (percentComplete) { + return percentComplete - Math.sin(percentComplete*2*Math.PI) / (2*Math.PI); + }; +}; + +AnimationTimer.makeElastic = function (passes) { + passes = passes || 3; + return function (percentComplete) { + return ((1-Math.cos(percentComplete * Math.PI * passes)) * + (1 - percentComplete)) + percentComplete; + }; +}; + +AnimationTimer.makeBounce = function (bounces) { + var fn = AnimationTimer.makeElastic(bounces); + return function (percentComplete) { + percentComplete = fn(percentComplete); + return percentComplete <= 1 ? percentComplete : 2-percentComplete; + }; +}; + +AnimationTimer.makeLinear = function () { + return function (percentComplete) { + return percentComplete; + }; +}; diff --git a/canvas/ch08/section-8.1.1/example.html b/canvas/ch08/section-8.1.1/example.html new file mode 100644 index 0000000..23653ec --- /dev/null +++ b/canvas/ch08/section-8.1.1/example.html @@ -0,0 +1,89 @@ + + + + + Bounding Box Collision Detection + + + + + + + Canvas not supported + + +
+ + canvas not supported + +
+ + + + + + + + diff --git a/canvas/ch08/section-8.1.1/example.js b/canvas/ch08/section-8.1.1/example.js new file mode 100644 index 0000000..fee8501 --- /dev/null +++ b/canvas/ch08/section-8.1.1/example.js @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + GRAVITY_FORCE = 9.81, // 9.81 m/s / s + CANVAS_HEIGHT_IN_METERS = 10, // 10 meters + pixelsPerMeter = canvas.height / CANVAS_HEIGHT_IN_METERS; + + thrustersCanvas = document.getElementById('thrustersCanvas'), + thrustersContext = thrustersCanvas.getContext('2d'), + + lastTime = 0, // Time of last animation frame + fps = 60, // Frames/second + + // AnimationTimers.................................................... + + PUSH_ANIMATION_DURATION = 800, + pushAnimationTimer = new AnimationTimer(PUSH_ANIMATION_DURATION), + fallingAnimationTimer = new AnimationTimer(), + + // Ledge information.............................................. + + ledgeInfos = [ + { left: 40, top: 75, width: 50, height: 12, color: 'rgb(255,255,0)' }, + { left: 220, top: 455, width: 50, height: 12, color: 'rgb(100,80,205)' } + ], + + ledges = [ledgeInfos.length], + + // Ball behaviors................................................ + + LEFT = 1, + RIGHT = 2, + arrow = undefined, + + BALL_RADIUS = 18, + BALL_INITIAL_LOCATION = { + top: ledgeInfos[0].top - BALL_RADIUS*2, + left: ledgeInfos[0].left + + ledgeInfos[0].width/2 - BALL_RADIUS + }, + + fallOnLedge = { + ballWillHitLedge: function (ledge) { + var ballRight = ball.left + ball.width, + ledgeRight = ledge.left + ledge.width, + ballBottom = ball.top + ball.height, + nextBallBottomEstimate = ballBottom + ball.velocityY / fps; + + return ballRight > ledge.left && + ball.left < ledgeRight && + ballBottom < ledge.top && + nextBallBottomEstimate > ledge.top; + }, + + execute: function (sprite, context, time) { + if (isBallFalling()) { + ledges.forEach(function (ledge) { + if (fallOnLedge.ballWillHitLedge(ledge)) { // this var. is DOMWindow + fallingAnimationTimer.stop(); + pushAnimationTimer.stop(); + + sprite.top = ledge.top - sprite.height; + sprite.velocityY = 0; + } + }); + } + } + }, + + moveBall = { + execute: function (sprite, context, time) { + if (pushAnimationTimer.isRunning()) { + if (arrow === LEFT) ball.left -= ball.velocityX / fps; + else ball.left += ball.velocityX / fps; + + if (getLedgeUnderBall()) { + if (pushAnimationTimer.getElapsedTime() > 800) { + pushAnimationTimer.stop(); + } + if (pushAnimationTimer.getElapsedTime() > 200) { + pushAnimationTimer.stop(); + } + } + else if ( ! isBallFalling()) { + startFalling(); + } + } + + if (isBallFalling()) { + ball.velocityY = GRAVITY_FORCE * + (fallingAnimationTimer.getElapsedTime()/1000) * pixelsPerMeter; + + ball.top += ball.velocityY / fps; + + if (ball.top > canvas.height) { + stopFalling(); + } + } + } + }, + + // Ball sprite................................................... + + ball = new Sprite('ball', + { + paint: function (sprite, context) { + context.save(); + context.beginPath(); + context.arc(sprite.left + sprite.width/2, + sprite.top + sprite.height/2, + BALL_RADIUS, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgba(0,0,255,0.7)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.lineWidth = 2; + context.strokeStyle = 'rgba(100,100,195,0.8)'; + context.stroke(); + + context.beginPath(); + context.arc(sprite.left + sprite.width/2, + sprite.top + sprite.height/2, + BALL_RADIUS/2, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgba(255,255,0,1.0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + context.stroke(); + + context.restore(); + } + }, + + [ fallOnLedge, moveBall ] + ); + +// Functions..................................................... + +function paintBall(x, y, w, h, radius, context) { + context.save(); + context.beginPath(); + context.arc(x + w/2, y + h/2, + radius, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgba(0,0,0,1.0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + + context.lineWidth = 2; + context.strokeStyle = 'rgba(100,100,195,0.8)'; + context.stroke(); + + context.beginPath(); + context.arc(x + w/2, y + h/2, + radius/2, 0, Math.PI*2, false); + context.clip(); + + context.shadowColor = 'rgba(255,255,0,1.0)'; + context.shadowOffsetX = -4; + context.shadowOffsetY = -4; + context.shadowBlur = 8; + context.stroke(); + + context.restore(); +} + +function paintLedge(sprite, context, color) { + context.save(); + context.shadowColor = 'rgba(0,0,0,0.5)'; + context.shadowBlur = 8; + context.shadowOffsetX = 2; + context.shadowOffsetY = 2; + context.fillStyle = color; + + context.fillRect(sprite.left,sprite.top,sprite.width,sprite.height); + context.restore(); +} + +function pushBallLeft() { + if (pushAnimationTimer.isRunning()) { + pushAnimationTimer.stop(); + ball.velocityX = ball.velocityX * 1.5; + } + arrow = LEFT; + pushAnimationTimer.start(); +} + +function pushBallRight() { + if (pushAnimationTimer.isRunning()) { + pushAnimationTimer.stop(); + ball.velocityX = ball.velocityX * 1.5; + } + arrow = RIGHT; + pushAnimationTimer.start(); +} + +function startFalling() { + fallingAnimationTimer.start(); + ball.velocityX = 110; + ball.velocityY = 200; +} + +function stopFalling() { + reset(); +} + +function reset() { + fallingAnimationTimer.stop(); + pushAnimationTimer.stop(); + + ball.left = BALL_INITIAL_LOCATION.left; + ball.top = BALL_INITIAL_LOCATION.top; + ball.velocityY = 0; +} + +function isBallFalling() { + return fallingAnimationTimer.isRunning(); +} + +function getLedgeUnderBall() { + var ledge; + + for (var i=0; i < ledges.length; i++) { + ledge = ledges[i]; + if (ball.left + 2*BALL_RADIUS > ledge.left && + ball.left < ledge.left + ledge.width && + ball.top + ball.height === ledge.top) { + return ledge; + } + } + return undefined; +} + +// Thrusters..................................................... + +function paintThrusters() { + thrustersContext.clearRect(0,0, + thrustersCanvas.width,thrustersCanvas.height); + + if (pushAnimationTimer.isRunning()) + thrustersContext.fillStyle = 'rgb(255,255,0)'; + else + thrustersContext.fillStyle = 'rgba(100,140,230,0.3)'; + + if (arrow === LEFT) { + paintLeftArrow(thrustersContext); + thrustersContext.fillStyle = 'rgba(100,140,230,0.3)'; + paintRightArrow(thrustersContext); + } + else { + paintRightArrow(thrustersContext); + thrustersContext.fillStyle = 'rgba(100,140,230,0.3)'; + paintLeftArrow(thrustersContext); + } +} + +function paintRightArrow(context) { + thrustersContext.save(); + thrustersContext.translate(thrustersCanvas.width, 0); + thrustersContext.scale(-1,1); + paintLeftArrow(context); + thrustersContext.restore(); +} + +function paintLeftArrow(context) { + var ARROW_MARGIN = 10; + + context.beginPath(); + + context.moveTo( thrustersCanvas.width/2 - ARROW_MARGIN/2, + ARROW_MARGIN/2); + + context.lineTo( thrustersCanvas.width/2 - ARROW_MARGIN/2, + thrustersCanvas.height - ARROW_MARGIN); + + context.quadraticCurveTo( + thrustersCanvas.width/2 - ARROW_MARGIN/2, + thrustersCanvas.height - ARROW_MARGIN/2, + thrustersCanvas.width/2 - ARROW_MARGIN, + thrustersCanvas.height - ARROW_MARGIN/2); + + context.lineTo(ARROW_MARGIN, + thrustersCanvas.height/2 + ARROW_MARGIN/2); + + context.quadraticCurveTo( + ARROW_MARGIN - 3, + thrustersCanvas.height/2, + ARROW_MARGIN, + thrustersCanvas.height/2 - ARROW_MARGIN/2); + + context.lineTo(thrustersCanvas.width/2 - ARROW_MARGIN, + ARROW_MARGIN/2); + + context.quadraticCurveTo( + thrustersCanvas.width/2 - ARROW_MARGIN, + ARROW_MARGIN/2, + thrustersCanvas.width/2 - ARROW_MARGIN/2, + ARROW_MARGIN/2); + + context.fill(); + context.stroke(); +} + +// Animation functions........................................... + +function calculateFps(time) { + var fps = 1000 / (time - lastTime); + lastTime = time; + return fps; +} + +function updateSprites(time) { + ball.update(context, time); + ledges.forEach(function(ledge) { + ledge.update(context, time); + }); +} + +function paintSprites() { + ball.paint(context); + ledges.forEach(function(ledge) { + ledge.paint(context); + }); +} + +function animate(time) { + fps = calculateFps(time); + + context.clearRect(0,0,canvas.width,canvas.height); + updateSprites(time); + paintSprites(); + paintThrusters(); + + window.requestNextAnimationFrame(animate); +} + +// Event handlers................................................ + +thrustersCanvas.onmousedown = function canvasMouseDown(e) { + var rect = thrustersCanvas.getBoundingClientRect(), + x = e.x || e.clientX, + y = e.y || e.clientY; + + e.preventDefault(); + e.stopPropagation(); + + if (x-rect.left > thrustersCanvas.width/2) { + pushBallRight(); + } + else { + pushBallLeft(); + } +}; + +// Initialization................................................ + +var BALL_SLOW_VELOCITY_X = 30, + BALL_SLOW_VELOCITY_Y = 7, + BALL_VELOCITY_X = 110, + BALL_VELOCITY_Y = 200, + index = 0; + +thrustersContext.strokeStyle = 'rgba(100,140,230,0.6)'; +thrustersContext.shadowColor = 'rgba(0,0,0,0.3)'; +thrustersContext.shadowBlur = 6; +thrustersContext.shadowX = 4; +thrustersContext.shadowY = 4; + +ball.left = BALL_INITIAL_LOCATION.left; +ball.top = BALL_INITIAL_LOCATION.top; +ball.width = BALL_RADIUS*2; +ball.height = BALL_RADIUS*2; +ball.velocityX = 110; +ball.velocityY = 0; + +ledges.forEach(function(ledge) { + index = 0; + + ledgeInfos.forEach(function (ledgeInfo) { + ledge = new Sprite('ledge', { + paint: function (sprite, context) { + paintLedge(sprite, context, ledgeInfo.color); + } + }); + + ledge.left = ledgeInfo.left; + ledge.top = ledgeInfo.top; + ledge.width = ledgeInfo.width; + ledge.height = ledgeInfo.height; + + ledges[index] = ledge; + ++index; + }); +}); + +window.requestNextAnimationFrame(animate); diff --git a/canvas/ch08/section-8.1.1/sprites.js b/canvas/ch08/section-8.1.1/sprites.js new file mode 100644 index 0000000..008c60a --- /dev/null +++ b/canvas/ch08/section-8.1.1/sprites.js @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Painters................................................................... + +// Painters paint sprites with a paint(sprite, context) method. ImagePainters +// paint an image for their sprite. + +var ImagePainter = function (imageUrl) { + this.image = new Image; + this.image.src = imageUrl; +}; + +ImagePainter.prototype = { + drawImage: function(sprite, context) { + }, + + paint: function (sprite, context) { + if (this.image !== undefined) { + if ( ! this.image.complete) { + this.image.onload = function (e) { + context.drawImage(this, // this is image + sprite.left, sprite.top, + sprite.width, sprite.height); + }; + } + else { + context.drawImage(this.image, sprite.left, sprite.top, + sprite.width, sprite.height); + } + } + } +}; + +SpriteSheetPainter = function (cells) { + this.cells = cells; + this.cellIndex = 0; +}; + +SpriteSheetPainter.prototype = { + advance: function () { + if (this.cellIndex == this.cells.length-1) { + this.cellIndex = 0; + } + else { + this.cellIndex++; + } + }, + + paint: function (sprite, context) { + var cell = this.cells[this.cellIndex]; + + context.drawImage(spritesheet, cell.left, cell.top, cell.width, cell.height, + sprite.left, sprite.top, cell.width, cell.height); + } +}; + +// Sprite Animators........................................................... + +var SpriteAnimator = function (painters, elapsedCallback) { + this.painters = painters; + if (elapsedCallback) { + this.elapsedCallback = elapsedCallback; + } + this.animationTimer = new AnimationTimer(1000); + this.painters = []; + this.spritePainter = undefined; + this.index = 0; +}; + +SpriteAnimator.prototype = { + initializeSprite: function (sprite) { + sprite.animating = true; + sprite.painter = this.painters[0]; + }, + + endAnimation: function(interval, originalPainter) { + sprite.animating = false; + + if (this.elapsedCallback) this.elapsedCallback(sprite); + else sprite.painter = originalPainter; + + clearInterval(interval); + }, + + advanceSpritePainter: function (sprite) { + sprite.painter = this.painters[this.index]; + }, + + start: function (sprite, duration) { + var period = duration / (this.painters.length), + interval = undefined, + animator = this, // for setInterval() function + originalPainter = sprite.painter; + + initializeSprite(sprite); + + animationTimer.duration = duration; + animationTimer.start(); + + interval = setInterval(function() { + var elapsed = animationTimer.getElapsedTime(); + animator.index = (parseFloat(elapsed / period).toFixed(0)) - 1; + + if (animationTimer.isOver()) endAnimation(interval, originalPainter); + else advanceSpritePainter(sprite); + }, period); + }, +}; + +// Sprites.................................................................... + +// Sprites have a name, a painter, and an array of behaviors. Sprites can +// be updated, and painted. +// +// A sprite's painter paints the sprite: paint(sprite, context) +// A sprite's behavior executes: execute(sprite, context, time) + +var Sprite = function (name, painter, behaviors) { + if (name !== undefined) this.name = name; + else this.name = undefined; + + if (painter !== undefined) this.painter = painter; + else this.painter = undefined; + + if (behaviors !== undefined) this.behaviors = behaviors; + else this.behaviors = []; + + this.left = 0; + this.top = 0; + this.width = 10; + this.height = 10; + this.velocityX = 0; + this.velocityY = 0; + this.visible = true; + this.animating = false; + + return this; +}; + +Sprite.prototype = { + paint: function (context) { + if (this.painter !== undefined && this.visible) { + this.painter.paint(this, context); + } + }, + + update: function (context, time) { + for (var i = this.behaviors.length; i > 0; --i) { + this.behaviors[i-1].execute(this, context, time); + } + } +}; diff --git a/canvas/ch08/section-8.1.1/stopwatch.js b/canvas/ch08/section-8.1.1/stopwatch.js new file mode 100644 index 0000000..af7e9d7 --- /dev/null +++ b/canvas/ch08/section-8.1.1/stopwatch.js @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Stopwatch.................................................................. +// +// Like the real thing, you can start and stop a stopwatch, and you can +// find out the elapsed time the stopwatch has been running. After you stop +// a stopwatch, it's getElapsedTime() method returns the elapsed time +// between the start and stop. + +Stopwatch = function () { +}; + +// You can get the elapsed time while the timer is running, or after it's +// stopped. + +Stopwatch.prototype = { + startTime: 0, + running: false, + elapsedTime: 0, + + start: function () { + this.startTime = +new Date(); + this.elapsedTime = 0; + this.running = true; + }, + + stop: function () { + this.elapsedTime = +new Date() - this.startTime; + this.running = false; + }, + + getElapsedTime: function () { + if (this.running) return +new Date() - this.startTime; + else return this.elapsedTime; + }, + + reset: function() { + this.elapsedTime = 0; + this.startTime = 0; + this.running = false; + } +}; diff --git a/canvas/ch08/section-8.3/example.html b/canvas/ch08/section-8.3/example.html new file mode 100644 index 0000000..8044d15 --- /dev/null +++ b/canvas/ch08/section-8.3/example.html @@ -0,0 +1,94 @@ + + + + + + Ray Casting + + + + + + + Canvas not supported + + +
0
+ +
+ Launch velocity (m/s): m/s
+ Launch angle (degrees): degrees
+
+ + + + + + diff --git a/canvas/ch08/section-8.3/example.js b/canvas/ch08/section-8.3/example.js new file mode 100644 index 0000000..fb42b86 --- /dev/null +++ b/canvas/ch08/section-8.3/example.js @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + scoreboard = document.getElementById('scoreboard'), + launchVelocityOutput = document.getElementById('launchVelocityOutput'), + launchAngleOutput = document.getElementById('launchAngleOutput'), + + elapsedTime = undefined, + launchTime = undefined, + + score = 0, + lastScore = 0, + lastMouse = { left: 0, top: 0 }, + + threePointer = false, + needInstructions = true, + + LAUNCHPAD_X = 50, + LAUNCHPAD_Y = context.canvas.height-50, + LAUNCHPAD_WIDTH = 50, + LAUNCHPAD_HEIGHT = 12, + ARENA_LENGTH_IN_METERS = 10, + INITIAL_LAUNCH_ANGLE = Math.PI/4, + + launchAngle = INITIAL_LAUNCH_ANGLE, + pixelsPerMeter = canvas.width / ARENA_LENGTH_IN_METERS, + + // LaunchPad................................................. + + launchPadPainter = { + LAUNCHPAD_FILL_STYLE: 'rgb(100,140,230)', + + paint: function (ledge, context) { + context.save(); + context.fillStyle = this.LAUNCHPAD_FILL_STYLE; + context.fillRect(LAUNCHPAD_X, LAUNCHPAD_Y, + LAUNCHPAD_WIDTH, LAUNCHPAD_HEIGHT); + context.restore(); + } + }, + + launchPad = new Sprite('launchPad', launchPadPainter), + + // Ball...................................................... + + BALL_RADIUS = 8, + lastBallPosition = { left: 0, top: 0 }, + + ballPainter = { + BALL_FILL_STYLE: 'rgb(255,255,0)', + BALL_STROKE_STYLE: 'rgb(0,0,0,0.4)', + + paint: function (ball, context) { + context.save(); + context.shadowColor = undefined; + context.lineWidth = 2; + context.fillStyle = this.BALL_FILL_STYLE; + context.strokeStyle = this.BALL_STROKE_STYLE; + + context.beginPath(); + context.arc(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS, + ball.radius, 0, Math.PI*2, false); + + context.clip(); + context.fill(); + context.stroke(); + context.restore(); + } + }, + + // Lob behavior.............................................. + + lob = { + lastTime: 0, + GRAVITY_FORCE: 9.81, // m/s/s + + applyGravity: function (elapsed) { + ball.velocityY = (this.GRAVITY_FORCE * elapsed) - + (launchVelocity * Math.sin(launchAngle)); + }, + + updateBallPosition: function (updateDelta) { + lastBallPosition.left = ball.left; + lastBallPosition.top = ball.top; + + ball.left += ball.velocityX * (updateDelta) * pixelsPerMeter; + ball.top += ball.velocityY * (updateDelta) * pixelsPerMeter; + }, + + checkForThreePointer: function () { + if (ball.top < 0) { + threePointer = true; + } + }, + + checkBallBounds: function () { + if (ball.top > canvas.height || ball.left > canvas.width) { + reset(); + } + }, + + execute: function (ball, context, time) { + var updateDelta, + elapsedFlightTime; + + if (ballInFlight) { + elapsedFrameTime = (time - this.lastTime)/1000, + elapsedFlightTime = (time - launchTime)/1000; + + this.applyGravity(elapsedFlightTime); + this.updateBallPosition(elapsedFrameTime); + this.checkForThreePointer(); + this.checkBallBounds(); + } + this.lastTime = time; + } + }, + + ball = new Sprite('ball', ballPainter, [ lob ]), + ballInFlight = false, + + // Bucket.................................................... + + BUCKET_LEFT = 668, + BUCKET_TOP = canvas.height - 100, + BUCKET_WIDTH = 83, + BUCKET_HEIGHT = 62, + bucketHitCenter = { x: BUCKET_LEFT + 2*this.BUCKET_WIDTH/3, + y: BUCKET_TOP + BUCKET_HEIGHT/8 + }, + + bucketHitRadius = BUCKET_WIDTH/8, + bucketImage = new Image(), + + catchBall = { + intersectionPoint: { x: 0, y: 0 }, + + isBallInBucket: function() { // a posteriori + if (lastBallPosition.left === ball.left || + lastBallPosition.top === ball.top) { + return; + } + + var x1 = lastBallPosition.left, + y1 = lastBallPosition.top, + x2 = ball.left, + y2 = ball.top, + x3 = BUCKET_LEFT + BUCKET_WIDTH/4, + y3 = BUCKET_TOP, + x4 = BUCKET_LEFT + BUCKET_WIDTH, + y4 = y3, + m1 = (ball.top - lastBallPosition.top) / (ball.left - lastBallPosition.left), + + m2 = (y4 - y3) / (x4 - x3), // zero, but calculate anyway for illustration + + b1 = y1 - m1*x1, + b2 = y3 - m2*x3; + + this.intersectionPoint.x = (b2 - b1) / (m1 - m2); + this.intersectionPoint.y = m1*this.intersectionPoint.x + b1; + + return this.intersectionPoint.x > x3 && + this.intersectionPoint.x < x4 && + ball.top + ball.height > y3 && + ball.left + ball.width < x4; + }, + + adjustScore: function() { + if (threePointer) lastScore = 3; + else lastScore = 2; + + score += lastScore; + scoreboard.innerText = score; + }, + + drawRay: function() { + context.beginPath(); + context.save(); + context.lineWidth = 1; + context.strokeStyle = 'blue'; + context.moveTo(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS); + context.lineTo(this.intersectionPoint.x, this.intersectionPoint.y); + context.stroke(); + context.restore(); + }, + + execute: function (bucket, context, time) { + if (ballInFlight) { + this.drawRay(); + + if (this.isBallInBucket()) { + reset(); + this.adjustScore(); + } + } + } + }, + + bucket = new Sprite('bucket', { + paint: function (sprite, context) { + context.drawImage(bucketImage, BUCKET_LEFT, BUCKET_TOP); + } + }, + + [ catchBall ] + ); + +// Functions..................................................... + +function freeze() { + ball.velocityX = 0; + ball.velocityY = 0; + ballInFlight = false; + needInstructions = false; +} + +function reset() { + lastBallPosition.left = ball.left; + lastBallPosition.top = ball.top; + + ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2 - BALL_RADIUS; + ball.top = LAUNCHPAD_Y - ball.height/2 - BALL_RADIUS; + + ball.velocityX = 0; + ball.velocityY = 0; + ballInFlight = false; + needInstructions = false; + lastScore = 0; +} + +function showText(text) { + var metrics; + + context.font = '42px Helvetica'; + metrics = context.measureText(text); + + context.save(); + context.shadowColor = undefined; + context.strokeStyle = 'rgb(80,120,210)'; + context.fillStyle = 'rgba(100,140,230,0.5)'; + + context.fillText(text, + canvas.width/2 - metrics.width/2, + canvas.height/2); + + context.strokeText(text, + canvas.width/2 - metrics.width/2, + canvas.height/2); + context.restore(); +} + +function drawRubberband() { + context.beginPath(); + context.moveTo(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS); + context.lineTo(bucketHitCenter.x, bucketHitCenter.y); + context.stroke(); +}; + +function drawGuidewire() { + context.moveTo(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS); + context.lineTo(lastMouse.left, lastMouse.top); + context.stroke(); +}; + +function updateBackgroundText() { + if (lastScore == 3) showText('Three pointer!'); + else if (lastScore == 2) showText('Nice shot!'); + else if (needInstructions) showText('Click to launch ball'); +}; + +function resetScoreLater() { + setTimeout(function () { + lastScore = 0; + }, 1000); +}; + +function updateSprites(time) { + bucket.update(context, time); + launchPad.update(context, time); + ball.update(context, time); +} + +function paintSprites() { + launchPad.paint(context); + bucket.paint(context); + ball.paint(context); +} + +function mouseToCanvas(e) { + var rect = canvas.getBoundingClientRect(), + loc = { x: e.x || e.clientX, + y: e.y || e.clientY + }; + + loc.x -= rect.left; + loc.y -= rect.top; + + return loc; +} + +// Event handlers................................................ + +canvas.onmousedown = function(e) { + var rect; + + e.preventDefault(); + + if ( ! ballInFlight) { + ball.velocityX = launchVelocity * Math.cos(launchAngle); + ball.velocityY = launchVelocity * Math.sin(launchAngle); + ballInFlight = true; + threePointer = false; + launchTime = +new Date(); + } +}; + +canvas.onmousemove = function (e) { + var rect; + + e.preventDefault(); + + if ( ! ballInFlight) { + loc = mouseToCanvas(e); + lastMouse.left = loc.x; + lastMouse.top = loc.y; + + deltaX = Math.abs(lastMouse.left - ball.left); + deltaY = Math.abs(lastMouse.top - ball.top); + + launchAngle = Math.atan(parseFloat(deltaY) / parseFloat(deltaX)); + launchVelocity = 4 * deltaY / Math.sin(launchAngle) / pixelsPerMeter; + + launchVelocityOutput.innerText = launchVelocity.toFixed(2); + launchAngleOutput.innerText = (launchAngle * 180/Math.PI).toFixed(2); + } +}; + +// Animation Loop................................................ + +function animate() { + var time = Date.now(); + + elapsedTime = (time - launchTime) / 1000; + context.clearRect(0,0,canvas.width,canvas.height); + + if (!ballInFlight) { + drawGuidewire(); + updateBackgroundText(); + + if (lastScore !== 0) { // just scored + resetScoreLater(); + } + } + + paintSprites(); + updateSprites(time); + + //drawRubberband(); + + context.save(); + context.beginPath(); + context.lineWidth = 0.5; + context.strokeStyle = 'red'; + context.moveTo(0, BUCKET_TOP+0.5); + context.lineTo(canvas.width, BUCKET_TOP); + context.stroke(); + context.restore(); + + window.requestNextAnimationFrame(animate); +} + +// Initialization................................................ + +ball.width = BALL_RADIUS*2; +ball.height = ball.width; +ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2 - BALL_RADIUS; +ball.top = LAUNCHPAD_Y - ball.height/2 - BALL_RADIUS; +lastBallPosition.left = ball.left; +lastBallPosition.top = ball.top; +ball.radius = BALL_RADIUS; + +context.lineWidth = 0.5; +context.strokeStyle = 'rgba(0,0,0,0.5)'; +context.shadowColor = 'rgba(0,0,0,0.5)'; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.shadowBlur = 4; +context.stroke(); + +bucketImage.src = '../../shared/images/bucket.png'; +bucketImage.onload = function (e) { + bucket.left = BUCKET_LEFT; + bucket.top = BUCKET_TOP; + bucket.width = BUCKET_WIDTH; + bucket.height = BUCKET_HEIGHT; +}; + +animate(); diff --git a/canvas/ch08/section-8.4.1.6/example.html b/canvas/ch08/section-8.4.1.6/example.html new file mode 100644 index 0000000..b2872f5 --- /dev/null +++ b/canvas/ch08/section-8.4.1.6/example.html @@ -0,0 +1,58 @@ + + + + + + Using SAT with Images and Sprites + + + + + + + Canvas not supported + + + + + + + + diff --git a/canvas/ch08/section-8.4.1.6/example.js b/canvas/ch08/section-8.4.1.6/example.js new file mode 100644 index 0000000..714ea06 --- /dev/null +++ b/canvas/ch08/section-8.4.1.6/example.js @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + shapes = [], + polygonPoints = [ + [ new Point(250, 150), new Point(250, 250), + new Point(350, 250) ], + + [ new Point(100, 100), new Point(100, 150), + new Point(150, 150), new Point(150, 100) ], + + [ new Point(400, 100), new Point(380, 150), + new Point(500, 150), new Point(520, 100) ] + ], + + polygonStrokeStyles = [ 'blue', 'yellow', 'red'], + polygonFillStyles = [ 'rgba(255,255,0,0.7)', + 'rgba(100,140,230,0.6)', + 'rgba(255,255,255,0.8)' ], + + mousedown = { x: 0, y: 0 }, + lastdrag = { x: 0, y: 0 }, + shapeBeingDragged = undefined; + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function drawShapes() { + shapes.forEach( function (shape) { + shape.stroke(context); + shape.fill(context); + }); +} + +canvas.onmousedown = function (e) { + var location = windowToCanvas(e); + + shapes.forEach( function (shape) { + if (shape.isPointInPath(context, location.x, location.y)) { + shapeBeingDragged = shape; + mousedown.x = location.x; + mousedown.y = location.y; + lastdrag.x = location.x; + lastdrag.y = location.y; + } + }); +} + +function detectCollisions() { + var textY = 30; + + if (shapeBeingDragged) { + shapes.forEach( function (shape) { + if (shape !== shapeBeingDragged) { + if (shapeBeingDragged.collidesWith(shape)) { + context.fillStyle = shape.fillStyle; + context.fillText('collision', 20, textY); + textY += 40; + } + } + }); + } +}; + +canvas.onmousemove = function (e) { + var location, + dragVector; + + if (shapeBeingDragged !== undefined) { + location = windowToCanvas(e); + dragVector = { x: location.x - lastdrag.x, + y: location.y - lastdrag.y + }; + + shapeBeingDragged.move(dragVector.x, dragVector.y); + + lastdrag.x = location.x; + lastdrag.y = location.y; + + context.clearRect(0,0,canvas.width,canvas.height); + drawShapes(); + detectCollisions(); + } +} + +canvas.onmouseup = function (e) { + shapeBeingDragged = undefined; +} + +for (var i=0; i < polygonPoints.length; ++i) { + var polygon = new Polygon(), + points = polygonPoints[i]; + + polygon.strokeStyle = polygonStrokeStyles[i]; + polygon.fillStyle = polygonFillStyles[i]; + + points.forEach( function (point) { + polygon.addPoint(point.x, point.y); + }); + + shapes.push(polygon); +} + +var ballSprite = new Sprite('ball', new ImagePainter('tennis-ball.png')); +ballSprite.top = 100; +ballSprite.left = 200; +ballSprite.width = 79; +ballSprite.height = 79; + +shapes.push(new ImageShape('golfball.png', 50, 50)); +shapes.push(new SpriteShape(ballSprite, 100, 100)); + +context.shadowColor = 'rgba(100,140,255,0.5)'; +context.shadowBlur = 4; +context.shadowOffsetX = 2; +context.shadowOffsetY = 2; +context.font = '38px Arial'; + +drawShapes(); + +context.save(); +context.fillStyle = 'cornflowerblue'; +context.font = '24px Arial'; +context.fillText('Drag shapes over each other', 10, 25); +context.restore(); diff --git a/canvas/ch08/section-8.4.1.6/golfball.png b/canvas/ch08/section-8.4.1.6/golfball.png new file mode 100644 index 0000000..654e4a0 Binary files /dev/null and b/canvas/ch08/section-8.4.1.6/golfball.png differ diff --git a/canvas/ch08/section-8.4.1.6/shapes.js b/canvas/ch08/section-8.4.1.6/shapes.js new file mode 100644 index 0000000..0298941 --- /dev/null +++ b/canvas/ch08/section-8.4.1.6/shapes.js @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var Point = function (x, y) { + this.x = x; + this.y = y; +}; + +var Shape = function () { + this.x = undefined; + this.y = undefined; + this.strokeStyle = 'rgba(255, 253, 208, 0.9)'; + this.fillStyle = 'rgba(147, 197, 114, 0.8)'; +}; + +Shape.prototype = { + collidesWith: function (shape) { + var axes = this.getAxes().concat(shape.getAxes()); + return !this.separationOnAxes(axes, shape); + }, + + separationOnAxes: function (axes, shape) { + for (var i=0; i < axes.length; ++i) { + axis = axes[i]; + projection1 = shape.project(axis); + projection2 = this.project(axis); + + if (! projection1.overlaps(projection2)) { + return true; // don't have to test remaining axes + } + } + return false; + }, + + move: function (dx, dy) { + throw 'move(dx, dy) not implemented'; + }, + + createPath: function (context) { + throw 'createPath(context) not implemented'; + }, + + getAxes: function () { + throw 'getAxes() not implemented'; + }, + + project: function (axis) { + throw 'project(axis) not implemented'; + }, + + fill: function (context) { + context.save(); + context.fillStyle = this.fillStyle; + this.createPath(context); + context.fill(); + context.restore(); + }, + + stroke: function (context) { + context.save(); + context.strokeStyle = this.strokeStyle; + this.createPath(context); + context.stroke(); + context.restore(); + }, + + isPointInPath: function (context, x, y) { + this.createPath(context); + return context.isPointInPath(x, y); + }, +}; + +var Projection = function (min, max) { + this.min = min; + this.max = max; +}; + +Projection.prototype = { + overlaps: function (projection) { + return this.max > projection.min && projection.max > this.min; + } +}; + +var Vector = function(x, y) { + this.x = x; + this.y = y; +}; + +Vector.prototype = { + getMagnitude: function () { + return Math.sqrt(Math.pow(this.x, 2) + + Math.pow(this.y, 2)); + }, + + add: function (vector) { + var v = new Vector(); + v.x = this.x + vector.x; + v.y = this.y + vector.y; + return v; + }, + + subtract: function (vector) { + var v = new Vector(); + v.x = this.x - vector.x; + v.y = this.y - vector.y; + return v; + }, + + dotProduct: function (vector) { + return this.x * vector.x + + this.y * vector.y; + }, + + edge: function (vector) { + return this.subtract(vector); + }, + + perpendicular: function () { + var v = new Vector(); + v.x = this.y; + v.y = 0-this.x; + return v; + }, + + normalize: function () { + var v = new Vector(), + m = this.getMagnitude(); + v.x = this.x / m; + v.y = this.y / m; + return v; + }, + + normal: function () { + var p = this.perpendicular(); + return p.normalize(); + } +}; + +var Polygon = function () { + this.points = []; + this.strokeStyle = 'blue'; + this.fillStyle = 'white'; +}; + +Polygon.prototype = new Shape(); + +Polygon.prototype.getAxes = function () { + var v1 = new Vector(), + v2 = new Vector(), + axes = []; + + for (var i=0; i < this.points.length-1; i++) { + v1.x = this.points[i].x; + v1.y = this.points[i].y; + + v2.x = this.points[i+1].x; + v2.y = this.points[i+1].y; + + axes.push(v1.edge(v2).normal()); + } + + v1.x = this.points[this.points.length-1].x; + v1.y = this.points[this.points.length-1].y; + + v2.x = this.points[0].x; + v2.y = this.points[0].y; + + axes.push(v1.edge(v2).normal()); + + return axes; +}; + +Polygon.prototype.project = function (axis) { + var scalars = [], + v = new Vector(); + + this.points.forEach( function (point) { + v.x = point.x; + v.y = point.y; + scalars.push(v.dotProduct(axis)); + }); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Polygon.prototype.addPoint = function (x, y) { + this.points.push(new Point(x,y)); +}; + +Polygon.prototype.createPath = function (context) { + if (this.points.length === 0) + return; + + context.beginPath(); + context.moveTo(this.points[0].x, + this.points[0].y); + + for (var i=0; i < this.points.length; ++i) { + context.lineTo(this.points[i].x, + this.points[i].y); + } + + context.closePath(); +}; + +Polygon.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +Polygon.prototype.move = function (dx, dy) { + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +var ImageShape = function(imageSource, x, y, w, h) { + var self = this; + + this.image = new Image(); + this.imageLoaded = false; + this.points = [ new Point(x,y) ]; + this.x = x; + this.y = y; + + this.image.src = imageSource; + + this.image.addEventListener('load', function (e) { + self.setPolygonPoints(); + self.imageLoaded = true; + }, false); +} + +ImageShape.prototype = new Polygon(); + +ImageShape.prototype.fill = function (context) { }; + +ImageShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x + this.image.width, this.y)); + this.points.push(new Point(this.x + this.image.width, this.y + this.image.height)); + this.points.push(new Point(this.x, this.y + this.image.height)); +}; + +ImageShape.prototype.drawImage = function (context) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); +}; + +ImageShape.prototype.stroke = function (context) { + var self = this; + + if (this.imageLoaded) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); + } + else { + this.image.addEventListener('load', function (e) { + self.drawImage(context); + }, false); + } +}; + +var SpriteShape = function (sprite, x, y) { + this.sprite = sprite; + this.x = x; + this.y = y; + sprite.left = x; + sprite.top = y; + this.setPolygonPoints(); +}; + +SpriteShape.prototype = new Polygon(); + +SpriteShape.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } + this.sprite.left = this.points[0].x; + this.sprite.top = this.points[0].y; +}; + +SpriteShape.prototype.fill = function (context) { }; + +SpriteShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x, this.y)); + this.points.push(new Point(this.x + this.sprite.width, this.y)); + this.points.push(new Point(this.x + this.sprite.width, this.y + this.sprite.height)); + this.points.push(new Point(this.x, this.y + this.sprite.height)); +}; + +SpriteShape.prototype.stroke = function (context) { + this.sprite.paint(context); +}; diff --git a/canvas/ch08/section-8.4.1.6/sprites.js b/canvas/ch08/section-8.4.1.6/sprites.js new file mode 100644 index 0000000..c8ba3cd --- /dev/null +++ b/canvas/ch08/section-8.4.1.6/sprites.js @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Painters................................................................... + +// Painters paint sprites with a paint(sprite, context) method. ImagePainters +// paint an image for their sprite. + +var ImagePainter = function (imageUrl) { + this.image = new Image; + this.image.src = imageUrl; +}; + +ImagePainter.prototype = { + image: undefined, + + paint: function (sprite, context) { + if (this.image !== undefined) { + if ( ! this.image.complete) { + this.image.onload = function (e) { + sprite.width = this.width; + sprite.height = this.height; + + context.drawImage(this, // this is image + sprite.left, sprite.top, + sprite.width, sprite.height); + }; + } + else { + context.drawImage(this.image, sprite.left, sprite.top, + sprite.width, sprite.height); + } + } + } +}; + +SpriteSheetPainter = function (cells) { + this.cells = cells; +}; + +SpriteSheetPainter.prototype = { + cells: [], + cellIndex: 0, + + advance: function () { + if (this.cellIndex == this.cells.length-1) { + this.cellIndex = 0; + } + else { + this.cellIndex++; + } + }, + + paint: function (sprite, context) { + var cell = this.cells[this.cellIndex]; + context.drawImage(spritesheet, cell.x, cell.y, cell.w, cell.h, + sprite.left, sprite.top, cell.w, cell.h); + } +}; + +// Sprite Animators........................................................... + +// Sprite animators have an array of painters that they succesively apply +// to a sprite over a period of time. Animators can be started with +// start(sprite, durationInMillis, restoreSprite) + +var SpriteAnimator = function (painters, elapsedCallback) { + this.painters = painters; + if (elapsedCallback) { + this.elapsedCallback = elapsedCallback; + } +}; + +SpriteAnimator.prototype = { + painters: [], + duration: 1000, + startTime: 0, + index: 0, + elapsedCallback: undefined, + + end: function (sprite, originalPainter) { + sprite.animating = false; + + if (this.elapsedCallback) { + this.elapsedCallback(sprite); + } + else { + sprite.painter = originalPainter; + } + }, + + start: function (sprite, duration) { + var endTime = +new Date() + duration, + period = duration / (this.painters.length), + interval = undefined, + animator = this, // for setInterval() function + originalPainter = sprite.painter; + + this.index = 0; + sprite.animating = true; + sprite.painter = this.painters[this.index]; + + interval = setInterval(function() { + if (+new Date() < endTime) { + sprite.painter = animator.painters[++animator.index]; + } + else { + animator.end(sprite, originalPainter); + clearInterval(interval); + } + }, period); + }, +}; + +// Sprites.................................................................... + +// Sprites have a name, a painter, and an array of behaviors. Sprites can +// be updated, and painted. +// +// A sprite's painter paints the sprite: paint(sprite, context) +// A sprite's behavior executes: execute(sprite, context, time) + +var Sprite = function (name, painter, behaviors) { + if (name !== undefined) this.name = name; + if (painter !== undefined) this.painter = painter; + if (behaviors !== undefined) this.behaviors = behaviors; + + return this; +}; + +Sprite.prototype = { + left: 0, + top: 0, + width: 10, + height: 10, + velocityX: 0, + velocityY: 0, + visible: true, + animating: false, + painter: undefined, // object with paint(sprite, context) + behaviors: [], // objects with execute(sprite, context, time) + + paint: function (context) { + if (this.painter !== undefined && this.visible) { + this.painter.paint(this, context); + } + }, + + update: function (context, time) { + for (var i = this.behaviors.length; i > 0; --i) { + this.behaviors[i-1].execute(this, context, time); + } + } +}; diff --git a/canvas/ch08/section-8.4.1.6/tennis-ball.png b/canvas/ch08/section-8.4.1.6/tennis-ball.png new file mode 100644 index 0000000..17e08a5 Binary files /dev/null and b/canvas/ch08/section-8.4.1.6/tennis-ball.png differ diff --git a/canvas/ch09/pinball/animationTimer.js b/canvas/ch09/pinball/animationTimer.js new file mode 100644 index 0000000..866302b --- /dev/null +++ b/canvas/ch09/pinball/animationTimer.js @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// AnimationTimer.................................................................. +// +// An animation runs for a duration, in milliseconds. It's up to you, +// however, to start and stop the animation -- animations do not stop +// automatically. You can check to see if an animation is over with the +// isOver() method, and you can see if an animation is running with +// isRunning(). Note that animations can be over, but still running. +// +// You can also supply an optional timeWarp function that warps the percent +// completed for the animation. That warping lets you do easily incorporate +// non-linear motion, such as: ease-in, ease-out, elastic, etc. + +AnimationTimer = function (duration, timeWarp) { + this.timeWarp = timeWarp; + + if (duration !== undefined) this.duration = duration; + else this.duration = 1000; + + this.stopwatch = new Stopwatch(); +}; + +AnimationTimer.prototype = { + start: function () { + this.stopwatch.start(); + }, + + stop: function () { + this.stopwatch.stop(); + }, + + getRealElapsedTime: function () { + return this.stopwatch.getElapsedTime(); + }, + + getElapsedTime: function () { + var elapsedTime = this.stopwatch.getElapsedTime(), + percentComplete = elapsedTime / this.duration; + + if (!this.stopwatch.running) return undefined; + if (this.timeWarp == undefined) return elapsedTime; + + return elapsedTime * (this.timeWarp(percentComplete) / percentComplete); + }, + + isRunning: function() { + return this.stopwatch.running; + }, + + isOver: function () { + return this.stopwatch.getElapsedTime() > this.duration; + }, + + reset: function() { + this.stopwatch.reset(); + } +}; + +AnimationTimer.makeEaseOut = function (strength) { + return function (percentComplete) { + return 1 - Math.pow(1 - percentComplete, strength*2); + }; +}; + +AnimationTimer.makeEaseIn = function (strength) { + return function (percentComplete) { + return Math.pow(percentComplete, strength*2); + }; +}; + +AnimationTimer.makeEaseInOut = function () { + return function (percentComplete) { + return percentComplete - Math.sin(percentComplete*2*Math.PI) / (2*Math.PI); + }; +}; + +AnimationTimer.makeElastic = function (passes) { + passes = passes || 3; + return function (percentComplete) { + return ((1-Math.cos(percentComplete * Math.PI * passes)) * + (1 - percentComplete)) + percentComplete; + }; +}; + +AnimationTimer.makeBounce = function (bounces) { + var fn = AnimationTimer.makeElastic(bounces); + return function (percentComplete) { + percentComplete = fn(percentComplete); + return percentComplete <= 1 ? percentComplete : 2-percentComplete; + }; +}; + +AnimationTimer.makeLinear = function () { + return function (percentComplete) { + return percentComplete; + }; +}; diff --git a/canvas/ch09/pinball/gameEngine.js b/canvas/ch09/pinball/gameEngine.js new file mode 100644 index 0000000..c40d5fc --- /dev/null +++ b/canvas/ch09/pinball/gameEngine.js @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var getTimeNow = function () { + return +new Date(); +}; + +// Game....................................................................... + +// This game engine implements a game loop that draws sprites. See sprites.js. +// +// The game engine also has support for: +// +// Time-based motion (game.pixelsPerFrame()) +// Pause (game.togglePaused()) +// High Scores (game.setHighScore(), game.getHighScores(), game.clearHighScores()) +// Sound (game.canPlaySound(), game.playSound()) +// Accessing frame rate (game.fps) +// Accessing game time (game.gameTime) +// Key processing (game.addKeyListener()) +// +// The game engine's animate() method invokes the following methods, +// in the order listed: +// +// game.startAnimate() +// game.paintUnderSprites() +// game.paintOverSprites() +// game.endAnimate() +// +// Those four methods are implemented by the game engine to do nothing. +// You override those do-nothing implementations to make the game come alive. + +var Game = function (gameName, canvasId) { + var canvas = document.getElementById(canvasId), + self = this; // Used by key event handlers below + + // General + + this.context = canvas.getContext('2d'); + this.gameName = gameName; + this.sprites = []; + this.keyListeners = []; + + // High scores + + this.HIGH_SCORES_SUFFIX = '_highscores'; + + // Image loading + + this.imageLoadingProgressCallback; + this.images = {}; + this.imageUrls = []; + this.imagesLoaded = 0; + this.imagesFailedToLoad = 0; + this.imagesIndex = 0; + + // Time + + this.startTime = 0; + this.lastTime = 0; + this.gameTime = 0; + this.fps = 0; + this.STARTING_FPS = 60; + + this.paused = false; + this.startedPauseAt = 0; + this.PAUSE_TIMEOUT = 100; + + // Sound + + this.soundOn = true; + this.soundChannels = []; + this.audio = new Audio(); + this.NUM_SOUND_CHANNELS = 10; + + for (var i=0; i < this.NUM_SOUND_CHANNELS; ++i) { + var audio = new Audio(); + this.soundChannels.push(audio); + } + + // The this object in the following event handlers is the + // DOM window, which is why the functions call + // self.keyPressed() instead of this.keyPressed(e). + + window.onkeypress = function (e) { self.keyPressed(e) }; + window.onkeydown = function (e) { self.keyPressed(e); }; + + return this; +}; + +// Game methods............................................................... + +Game.prototype = { + // Given a URL, return the associated image + + getImage: function (imageUrl) { + return this.images[imageUrl]; + }, + + // This method is called by loadImage() when + // an image loads successfully. + + imageLoadedCallback: function (e) { + this.imagesLoaded++; + }, + + // This method is called by loadImage() when + // an image does not load successfully. + + imageLoadErrorCallback: function (e) { + this.imagesFailedToLoad++; + }, + + // Loads a particular image + + loadImage: function (imageUrl) { + var image = new Image(), + self = this; // load and error event handlers called by DOMWindow + + image.src = imageUrl; + + image.addEventListener('load', + function (e) { + self.imageLoadedCallback(e); + }); + + image.addEventListener('error', + function (e) { + self.imageLoadErrorCallback(e); + }); + + this.images[imageUrl] = image; + }, + + // You call this method repeatedly to load images that have been + // queued (by calling queueImage()). This method returns the + // percent of the games images that have been processed. When + // the method returns 100, all images are loaded, and you can + // quit calling this method. + + loadImages: function () { + + // If there are images left to load + + if (this.imagesIndex < this.imageUrls.length) { + this.loadImage(this.imageUrls[this.imagesIndex]); + this.imagesIndex++; + } + + // Return the percent complete + + return (this.imagesLoaded + this.imagesFailedToLoad) / + this.imageUrls.length * 100; + }, + + // Call this method to add an image to the queue. The image + // will be loaded by loadImages(). + + queueImage: function (imageUrl) { + this.imageUrls.push(imageUrl); + }, + + // Game loop.................................................................. + + // Starts the animation by invoking window.requestNextAnimationFrame(). + // + // window.requestNextAnimationFrame() is a polyfill method implemented in + // requestNextAnimationFrame.js. You pass requestNextAnimationFrame() a + // reference to a function that the browser calls when it's time to draw + // the next animation frame. + // + // When it's time to draw the next animation frame, the browser invokes + // the function that you pass to requestNextAnimationFrame(). Because that + // function is invoked by the browser (the window object, to be more exact), + // the this variable in that function will be the window object. We want + // the this variable to be the game instead, so we use JavaScript's built-in + // call() function to call the function, with the game specified as the + // this variable. + + start: function () { + var self = this; // The this variable is the game + this.startTime = getTimeNow(); // Record game's startTime (used for pausing) + + window.requestNextAnimationFrame( + function (time) { + // The this variable in this function is the window, not the game, + // which is why we do not simply do this: animate.call(time). + + self.animate.call(self, time); // self is the game + }); + }, + + // Drives the game's animation. This method is called by the browser when + // it's time for the next animation frame. + // + // If the game is paused, animate() reschedules another call to animate() + // in PAUSE_TIMEOUT (100) ms. + // + // If the game is not paused, animate() paints the next animation frame and + // reschedules another call to animate() when it's time to draw the + // next animation frame. + // + // The implementations of this.startAnimate(), this.paintUnderSprites(), + // this.paintOverSprites(), and this.endAnimate() do nothing. You override + // those methods to create the animation frame. + + animate: function (time) { + var self = this; // window.requestNextAnimationFrame() called by DOMWindow + + if (this.paused) { + // In PAUSE_TIMEOUT (100) ms, call this method again to see if the game + // is still paused. There's no need to check more frequently. + + setTimeout( function () { + window.requestNextAnimationFrame( + function (time) { + self.animate.call(self, time); + }); + }, this.PAUSE_TIMEOUT); + } + else { // Game is not paused + this.tick(time); // Update fps, game time + this.clearScreen(); // Clear the screen in preparation for next frame + + this.startAnimate(time); // Override as you wish + this.paintUnderSprites(); // Override as you wish + + this.updateSprites(time); // Invoke sprite behaviors + this.paintSprites(time); // Paint sprites in the canvas + + this.paintOverSprites(); // Override as you wish + this.endAnimate(); // Override as you wish + + this.lastTime = time; + + // Call this method again when it's time for the next animation frame + + window.requestNextAnimationFrame( + function (time) { + self.animate.call(self, time); // The this variable refers to the window + }); + } + }, + + // Update the frame rate, game time, and the last time the application + // drew an animation frame. + + tick: function (time) { + this.updateFrameRate(time); + this.gameTime = (getTimeNow()) - this.startTime; + }, + + // Update the frame rate, based on the amount of time it took + // for the last animation frame only. + + updateFrameRate: function (time) { + if (this.lastTime === 0) this.fps = this.STARTING_FPS; + else this.fps = 1000 / (time - this.lastTime); + }, + + // Clear the entire canvas. + + clearScreen: function () { + this.context.clearRect(0, 0, + this.context.canvas.width, this.context.canvas.height); + }, + + // Update all sprites. The sprite update() method invokes all + // of a sprite's behaviors. + + updateSprites: function (time) { + for(var i=0; i < this.sprites.length; ++i) { + var sprite = this.sprites[i]; + sprite.update(this.context, time); + }; + }, + + // Paint all visible sprites. + + paintSprites: function (time) { + for(var i=0; i < this.sprites.length; ++i) { + var sprite = this.sprites[i]; + if (sprite.visible) + sprite.paint(this.context); + }; + }, + + // Toggle the paused state of the game. If, after + // toggling, the paused state is unpaused, the + // application subtracts the time spent during + // the pause from the game's start time. That + // means the game picks up where it left off, + // without a potentially large jump in time. + + togglePaused: function () { + var now = getTimeNow(); + + this.paused = !this.paused; + + if (this.paused) { + this.startedPauseAt = now; + } + else { // not paused + // Adjust start time, so game starts where it left off when + // the user paused it. + + this.startTime = this.startTime + now - this.startedPauseAt; + this.lastTime = now; + } + }, + + // Given a velocity of some object, calculate the number of pixels to + // move that object for the current frame. + + pixelsPerFrame: function (time, velocity) { + // Sprites move a certain amount of pixels per frame (pixels/frame). + // This methods returns the amount of pixels a sprite should move + // for a given frame. Sprite velocity is measured in pixels / second, + // so: (pixels/second) * (second/frame) = pixels/frame: + + return velocity / this.fps; // pixels / frame + }, + + // High scores................................................................ + + // Returns an array of high scores from local storage. + + getHighScores: function () { + var key = this.gameName + this.HIGH_SCORES_SUFFIX, + highScoresString = localStorage[key]; + + if (highScoresString == undefined) { + localStorage[key] = JSON.stringify([]); + } + return JSON.parse(localStorage[key]); + }, + + // Sets the high score in local storage. + + setHighScore: function (highScore) { + var key = this.gameName + this.HIGH_SCORES_SUFFIX, + highScoresString = localStorage[key]; + + highScores.unshift(highScore); + localStorage[key] = JSON.stringify(highScores); + }, + + // Removes the high scores from local storage. + + clearHighScores: function () { + localStorage[this.gameName + this.HIGH_SCORES_SUFFIX] = JSON.stringify([]); + }, + + // Key listeners.............................................................. + + // Add a (key, listener) pair to the keyListeners array. + + addKeyListener: function (keyAndListener) { + this.keyListeners.push(keyAndListener); + }, + + // Given a key, return the associated listener. + + findKeyListener: function (key) { + var listener = undefined; + + for(var i=0; i < this.keyListeners.length; ++i) { + var keyAndListener = this.keyListeners[i], + currentKey = keyAndListener.key; + if (currentKey === key) { + listener = keyAndListener.listener; + } + }; + return listener; + }, + + // This method is the call back for key down and key press + // events. + + keyPressed: function (e) { + var listener = undefined, + key = undefined; + + switch (e.keyCode) { + // Add more keys as needed + + case 32: key = 'space'; break; + case 68: key = 'd'; break; + case 75: key = 'k'; break; + case 83: key = 's'; break; + case 80: key = 'p'; break; + case 37: key = 'left arrow'; break; + case 39: key = 'right arrow'; break; + case 38: key = 'up arrow'; break; + case 40: key = 'down arrow'; break; + } + + listener = this.findKeyListener(key); + if (listener) { // listener is a function + listener(); // invoke the listener function + } + }, + + // Sound...................................................................... + + // Returns true if the browser can play sounds in the ogg file format. + + canPlayOggVorbis: function () { + return "" != this.audio.canPlayType('audio/ogg; codecs="vorbis"'); + }, + + // Returns true if the browser can play sounds in the mp3 file format. + + canPlayMp3: function () { + return "" != this.audio.canPlayType('audio/mpeg'); + }, + + // Returns the first available sound channel from the array of sound channels. + + getAvailableSoundChannel: function () { + var audio; + + for (var i=0; i < this.NUM_SOUND_CHANNELS; ++i) { + audio = this.soundChannels[i]; + + if (audio.played.length === 0 || audio.ended) { + return audio; + } + } + return undefined; // all channels in use + }, + + // Given an identifier, play the associated sound. + + playSound: function (id) { + var channel = this.getAvailableSoundChannel(), + element = document.getElementById(id); + + if (channel && element) { + channel.src = element.src === '' ? element.currentSrc : element.src; + channel.load(); + channel.play(); + } + }, + + + // Sprites.................................................................... + + // Add a sprite to the game. The game engine will update the sprite and + // paint it (if it's visible) in the animate() method. + + addSprite: function (sprite) { + this.sprites.push(sprite); + }, + + // It's probably a good idea not to access sprites directly, because + // it's better to write generalized code that deals with all + // sprites, so this method should be used sparingly. + + getSprite: function (name) { + for(i in this.sprites) { + if (this.sprites[i].name === name) + return this.sprites[i]; + } + return null; + }, + + // Override the following methods as desired: + + startAnimate: function (time) { }, // These methods are called by + paintUnderSprites: function () { }, // animate() in the order they + paintOverSprites: function () { }, // are listed. Override them + endAnimate: function () { } // as you wish. +}; diff --git a/canvas/ch09/pinball/images/actuator-0.png b/canvas/ch09/pinball/images/actuator-0.png new file mode 100644 index 0000000..e537311 Binary files /dev/null and b/canvas/ch09/pinball/images/actuator-0.png differ diff --git a/canvas/ch09/pinball/images/actuator-1.png b/canvas/ch09/pinball/images/actuator-1.png new file mode 100644 index 0000000..d47878e Binary files /dev/null and b/canvas/ch09/pinball/images/actuator-1.png differ diff --git a/canvas/ch09/pinball/images/actuator-2.png b/canvas/ch09/pinball/images/actuator-2.png new file mode 100644 index 0000000..adbccd8 Binary files /dev/null and b/canvas/ch09/pinball/images/actuator-2.png differ diff --git a/canvas/ch09/pinball/images/actuator-3.png b/canvas/ch09/pinball/images/actuator-3.png new file mode 100644 index 0000000..f6fc7ec Binary files /dev/null and b/canvas/ch09/pinball/images/actuator-3.png differ diff --git a/canvas/ch09/pinball/images/actuator-4.png b/canvas/ch09/pinball/images/actuator-4.png new file mode 100644 index 0000000..6e49123 Binary files /dev/null and b/canvas/ch09/pinball/images/actuator-4.png differ diff --git a/canvas/ch09/pinball/images/actuator-5.png b/canvas/ch09/pinball/images/actuator-5.png new file mode 100644 index 0000000..42d9554 Binary files /dev/null and b/canvas/ch09/pinball/images/actuator-5.png differ diff --git a/canvas/ch09/pinball/images/actuator-6.png b/canvas/ch09/pinball/images/actuator-6.png new file mode 100644 index 0000000..c249ff1 Binary files /dev/null and b/canvas/ch09/pinball/images/actuator-6.png differ diff --git a/canvas/ch09/pinball/images/actuator-7.png b/canvas/ch09/pinball/images/actuator-7.png new file mode 100644 index 0000000..a6c0356 Binary files /dev/null and b/canvas/ch09/pinball/images/actuator-7.png differ diff --git a/canvas/ch09/pinball/images/background.png b/canvas/ch09/pinball/images/background.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/pinball/images/background.png differ diff --git a/canvas/ch09/pinball/images/ball.png b/canvas/ch09/pinball/images/ball.png new file mode 100644 index 0000000..b6a106c Binary files /dev/null and b/canvas/ch09/pinball/images/ball.png differ diff --git a/canvas/ch09/pinball/images/fiftyBumperBright.png b/canvas/ch09/pinball/images/fiftyBumperBright.png new file mode 100644 index 0000000..ab75285 Binary files /dev/null and b/canvas/ch09/pinball/images/fiftyBumperBright.png differ diff --git a/canvas/ch09/pinball/images/fiveHundredBumperBright.png b/canvas/ch09/pinball/images/fiveHundredBumperBright.png new file mode 100644 index 0000000..a0a6ca5 Binary files /dev/null and b/canvas/ch09/pinball/images/fiveHundredBumperBright.png differ diff --git a/canvas/ch09/pinball/images/fiveXBumperLeftBright.png b/canvas/ch09/pinball/images/fiveXBumperLeftBright.png new file mode 100644 index 0000000..90bc132 Binary files /dev/null and b/canvas/ch09/pinball/images/fiveXBumperLeftBright.png differ diff --git a/canvas/ch09/pinball/images/fiveXBumperRightBright.png b/canvas/ch09/pinball/images/fiveXBumperRightBright.png new file mode 100644 index 0000000..6c32bd8 Binary files /dev/null and b/canvas/ch09/pinball/images/fiveXBumperRightBright.png differ diff --git a/canvas/ch09/pinball/images/leftFlipper.png b/canvas/ch09/pinball/images/leftFlipper.png new file mode 100644 index 0000000..836e28f Binary files /dev/null and b/canvas/ch09/pinball/images/leftFlipper.png differ diff --git a/canvas/ch09/pinball/images/oneHundredBumperBright.png b/canvas/ch09/pinball/images/oneHundredBumperBright.png new file mode 100644 index 0000000..ae80fad Binary files /dev/null and b/canvas/ch09/pinball/images/oneHundredBumperBright.png differ diff --git a/canvas/ch09/pinball/images/oneXBumperLeftBright.png b/canvas/ch09/pinball/images/oneXBumperLeftBright.png new file mode 100644 index 0000000..de4d7f2 Binary files /dev/null and b/canvas/ch09/pinball/images/oneXBumperLeftBright.png differ diff --git a/canvas/ch09/pinball/images/oneXBumperRightBright.png b/canvas/ch09/pinball/images/oneXBumperRightBright.png new file mode 100644 index 0000000..9bf11ee Binary files /dev/null and b/canvas/ch09/pinball/images/oneXBumperRightBright.png differ diff --git a/canvas/ch09/pinball/images/pinball-with-flippers.png b/canvas/ch09/pinball/images/pinball-with-flippers.png new file mode 100644 index 0000000..f2f3f11 Binary files /dev/null and b/canvas/ch09/pinball/images/pinball-with-flippers.png differ diff --git a/canvas/ch09/pinball/images/pinball-withballs.png b/canvas/ch09/pinball/images/pinball-withballs.png new file mode 100644 index 0000000..42ba95b Binary files /dev/null and b/canvas/ch09/pinball/images/pinball-withballs.png differ diff --git a/canvas/ch09/pinball/images/rightFlipper.png b/canvas/ch09/pinball/images/rightFlipper.png new file mode 100644 index 0000000..cb4bc73 Binary files /dev/null and b/canvas/ch09/pinball/images/rightFlipper.png differ diff --git a/canvas/ch09/pinball/images/tryAgain.png b/canvas/ch09/pinball/images/tryAgain.png new file mode 100644 index 0000000..00c538c Binary files /dev/null and b/canvas/ch09/pinball/images/tryAgain.png differ diff --git a/canvas/ch09/pinball/images/twoXBumperLeftBright.png b/canvas/ch09/pinball/images/twoXBumperLeftBright.png new file mode 100644 index 0000000..d63836e Binary files /dev/null and b/canvas/ch09/pinball/images/twoXBumperLeftBright.png differ diff --git a/canvas/ch09/pinball/images/twoXBumperRightBright.png b/canvas/ch09/pinball/images/twoXBumperRightBright.png new file mode 100644 index 0000000..6606b64 Binary files /dev/null and b/canvas/ch09/pinball/images/twoXBumperRightBright.png differ diff --git a/canvas/ch09/pinball/pinball.css b/canvas/ch09/pinball/pinball.css new file mode 100644 index 0000000..d88816c --- /dev/null +++ b/canvas/ch09/pinball/pinball.css @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + + #readoutToast { + position: absolute; + left: 480px; + top: 60px; + color: white; + display: none; + } + + #loadingToast { + padding: 20px; + position: absolute; + left: 42px; + top: 180px; + width: 450px; + height: 150px; + display: block; + } + + #loadingToast .title { + padding-left: 125px; + font: 16px Arial; + } + + #loadingToast p { + margin-left: 10px; + margin-right: 10px; + color: black; + } + + #highScoreParagraph { + position: absolute; + left: 175px; + top: 0px; + color: red; + font-size: 4.5em; + margin-left: 70px; + } + + #highScoreToast { + position: absolute; + left: 20px; + top: 120px; + color: cornflowerblue; + margin-left: 150px; + margin-top: 20px; + } + + #gameCanvas { + margin: 20px; + background: lightskyblue; + /*background: rgba(120,168,249,0.7);*/ + position: absolute; + left: 0px; + top: 0px; + -webkit-box-shadow: rgba(100,100,100,0.5) 4px 4px 8px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + border: thin solid cornflowerblue; + } + + .floatingControls { + background: rgba(0, 0, 0, 0.1); + border: thin solid skyblue; + -webkit-box-shadow: rgba(0,0,0,0.3) 2px 2px 4px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + position: absolute; + } + + .floatingControls a { + font-size: 1.5em; + text-decoration: none; + color: rgba(255,255,0,0.6); + } + + .floatingControls a:hover { + color: rgba(255,255,0,1.0); + } + + #instructionsLink { + color: cornflowerblue; + text-decoration: none; + } + + #instructionsLink:hover { + color: rgba(255,255,0,1.0); + } + + #instructions p.title { + font-size: 1.5em; + color: blue; + } + + #instructions { + margin-left: 60px; + margin-top: 50px; + padding-left: 20px; + padding-right: 20px; + width: 700px; + height: 370px; + -webkit-box-shadow: rgba(0,0,0,0.3) 4px 4px 8px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + color: rgba(0, 0, 255, 0.8); + background: rgba(255, 255, 255, 0.8); + } + + #instructionsOkayButtonDiv { + left: 0px; + width: 100%; + text-align: center; + } + + #instructionsOkayButton { + margin-top: 30px; + } + + .toast { + background: rgba(255, 255, 255, 0.7); + border: thin solid skyblue; + -webkit-box-shadow: rgba(0,0,0,0.3) 2px 2px 4px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + position: absolute; + display: none; + } + + #pausedToast { + padding: 5px 40px 20px 40px; + margin-left: 172px; + margin-top: 100px; + color: blue; + } + + #pausedToast p { + color: blue; + } + + #pausedToast p.title { + font-size: 1.50em; + color: blue; + padding-left: 18px; + } + + #scoreToast { + background: #5a3716; + left: 25px; + top: 25px; + padding: 5px; + font-size: 1.25em; + color: yellow; + width: 3em; + text-align: center; + border: thin solid rgba(255,255,0,0.5); + } + + div .title { + color: blue; + } + + div p { + color: blue; + } + + div a { + text-decoration: none; + color: cornflowerblue; + } + + p.title { + font-size: 1.5em; + } + + #gameOverToast { + padding-left: 30px; + padding-right: 30px; + padding-bottom: 10px; + margin-left: 173px; + margin-top: 100px; + text-align: center; + display: none; + } + + #highScoreList { + color: rgba(0,0,255,0.6); + } + + #previousHighScoresTitle { + margin-top: 50px; + } + + #loadButtonSpan { + position: absolute; + left: 200px; + padding-top: 20px; + } + + .blurb { + padding: 10px; + display: block; + font: 12px Arial; + } + + #progressDiv { + padding-left: 55px; + padding-top: 45px; + } + + #loadingMessage { + color: navyblue; + font: 14px Helvetica; + padding-top: 20px; + padding-left: 183px; + } + + #showPolygonsOnlyToast { + background: rgba(255,255,255,0.5); + padding: 3px; + color: rgba(255,250,0,1.0); + text-align: center; + left: 423px; + top: 25px; + } diff --git a/canvas/ch09/pinball/pinball.html b/canvas/ch09/pinball/pinball.html new file mode 100644 index 0000000..19c2b9c --- /dev/null +++ b/canvas/ch09/pinball/pinball.html @@ -0,0 +1,139 @@ + + + + + + Poker Pinball + + + + + + + + + + + + + + + + + + + Canvas not supported + + +
+ + Polygons +
+ + + +
+ Core HTML5 Canvas Pinball + +
Loading...
+
+
+ + + +
+ + + + +
+

Paused

+

Click here to start

+
+ + + + +
+

Game Over


+

clear high scores

+ +
+ + + +

+ + + + + + + + + + + + + + + diff --git a/canvas/ch09/pinball/pinball.js b/canvas/ch09/pinball/pinball.js new file mode 100644 index 0000000..737931a --- /dev/null +++ b/canvas/ch09/pinball/pinball.js @@ -0,0 +1,1442 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var game = new Game('pinball', 'gameCanvas'), + applyGravityAndFriction = false; + + TRY_AGAIN_X = 255, + TRY_AGAIN_Y = 865, + TRY_AGAIN_RADIUS = 35, + showTryAgain = false, + + showingHighScores = true, + + // Flippers................................................... + + LEFT_FLIPPER = 1, + RIGHT_FLIPPER = 2, + + LEFT_FLIPPER_PIVOT_X = 143, + LEFT_FLIPPER_PIVOT_Y = 774, + + LEFT_FLIPPER_PIVOT_OFFSET_X = 28, + LEFT_FLIPPER_PIVOT_OFFSET_Y = 29, + + FLIPPER_RISE_DURATION = 25, + FLIPPER_FALL_DURATION = 175, + + MAX_FLIPPER_ANGLE = Math.PI/4, + + LEFT_FLIPPER_STRIKE_ZONE_LEFT = 175, + LEFT_FLIPPER_STRIKE_ZONE_RIGHT = 260, + + FLIPPER_BOTTOM = 870, + + leftFlipperRiseTimer = + new AnimationTimer(FLIPPER_RISE_DURATION, + AnimationTimer.makeEaseOut(3)), + leftFlipperFallTimer = + new AnimationTimer(FLIPPER_FALL_DURATION, + AnimationTimer.makeEaseIn(3)), + + rightFlipperRiseTimer = + new AnimationTimer(FLIPPER_RISE_DURATION, + AnimationTimer.makeEaseOut(3)), + rightFlipperFallTimer = + new AnimationTimer(FLIPPER_FALL_DURATION, + AnimationTimer.makeEaseIn(3)), + + leftFlipperAngle = 0, + rightFlipperAngle = 0, + + // Actuator................................................... + + ACTUATOR_LEFT = 468, + ACTUATOR_TOP = 839, + ACTUATOR_PLATFORM_WIDTH = 45, + ACTUATOR_PLATFORM_HEIGHT = 10, + + actuatorSprite = new Sprite('actuator', + new ImagePainter('images/actuator-0.png')), + + // Ball....................................................... + + BALL_LAUNCH_LEFT = ACTUATOR_LEFT + 3, + BALL_LAUNCH_TOP = ACTUATOR_TOP - 30, + LAUNCH_VELOCITY_Y = 200, + MAX_BALL_VELOCITY = 400, + MIN_BALL_VELOCITY = 3, + MIN_BALL_VELOCITY_OFF_FLIPPERS = 75, + GAME_HEIGHT_IN_METERS = 2, + GRAVITY = 9.8; // m/s/s + + lastBallPosition = new Point(), + + ballOutOfPlay = false, + + prepareForLaunch = function() { + ballSprite.left = BALL_LAUNCH_LEFT; + ballSprite.top = BALL_LAUNCH_TOP; + + ballSprite.velocityX = 0; + ballSprite.velocityY = 0; + + applyGravityAndFriction = false; + adjustRightBoundaryAfterLostBall(); + + launching = true; + }, + + brieflyShowTryAgainImage = function (milliseconds) { + showTryAgain = true; + + setTimeout( function (e) { + showTryAgain = false; + }, 2000); + }, + + applyFrictionAndGravity = function (time) { + var lastElapsedTime = time / 1000, + metersPerSecond = GRAVITY * lastElapsedTime * 0.1; + + if (Math.abs(ballSprite.velocityX) > MIN_BALL_VELOCITY) { + ballSprite.velocityX *= Math.pow(0.5, lastElapsedTime); + } + + if (Math.abs(ballSprite.velocityY) > MIN_BALL_VELOCITY) { + ballSprite.velocityY *= Math.pow(0.5, lastElapsedTime); + } + + ballSprite.velocityY += metersPerSecond * + parseFloat(game.context.canvas.height / GAME_HEIGHT_IN_METERS); + }, + + ballMover = { + execute: function (sprite, context, time) { + if (!game.paused && !loading) { + lastBallPosition.x = sprite.left; + lastBallPosition.y = sprite.top; + + if ( !launching && sprite.left < ACTUATOR_LEFT && + (sprite.top > FLIPPER_BOTTOM || sprite.top < 0)) { + ballOutOfPlay = true; + } + + sprite.left += game.pixelsPerFrame(time, sprite.velocityX); + sprite.top += game.pixelsPerFrame(time, sprite.velocityY); + } + }, + }, + + ballSprite = new Sprite('ball', + new ImagePainter('images/ball.png'), + [ ballMover ]), + + ballShape = new SpriteShape(ballSprite, ballSprite.width, ballSprite.height), + + // Extra balls................................................ + + EXTRA_BALLS_RIGHT = 430, + EXTRA_BALL_WIDTH = 36, + EXTRA_BALLS_BOTTOM = game.context.canvas.height - 55, + + // Launching.................................................. + + launching = false, + launchStep = 1, + LAUNCH_STEPS = 8, + launchImages = [], // filled in with images below + + // Loading.................................................... + + loading = false, // not yet, see the end of this file + loadingToast = document.getElementById('loadingToast'), + loadingToastTitle = document.getElementById('loadingToastTitle'), + loadMessage = document.getElementById('loadMessage'), + progressDiv = document.getElementById('progressDiv'), + progressbar = new COREHTML5.Progressbar(300, 23, 'rgba(0,0,0,0.5)', 100, 130, 250), + + // Score...................................................... + + scoreToast = document.getElementById('scoreToast'), + scoreReadout = document.getElementById('score'), + score = 0, + lastScore = 0, + lastScoreUpdate = undefined, + + // High Score................................................. + + HIGH_SCORES_DISPLAYED = 10, + + highScoreToast = document.getElementById('highScoreToast'), + highScoreParagraph = document.getElementById('highScoreParagraph'), + highScoreList = document.getElementById('highScoreList'), + previousHighScoresTitle = document.getElementById('previousHighScoresTitle'), + nameInput = document.getElementById('nameInput'), + addMyScoreButton = document.getElementById('addMyScoreButton'), + newGameButton = document.getElementById('newGameButton'), + newGameFromHighScoresButton = + document.getElementById('newGameFromHighScoresButton'), + clearHighScoresCheckbox = document.getElementById('clearHighScoresCheckbox'), + + // Lives...................................................... + + livesLeft = 3, + life = 100, + + // Paused..................................................... + + pausedToast = document.getElementById('pausedToast'), + + // Game Over.................................................. + + gameOverToast = document.getElementById('gameOverToast'), + gameOver = false, + + // Collision Detection........................................ + + shapes = [], + + flipperCollisionDetected = false, + + showPolygonsOnlyToast = document.getElementById('showPolygonsOnlyToast'), + showPolygonsOnlyCheckbox = document.getElementById('showPolygonsOnlyCheckbox'), + showPolygonsOnly = showPolygonsOnlyCheckbox.checked, + + fiveHundredBumper = new Circle(256, 187, 40), + oneHundredBumperRight = new Circle(395, 328, 40), + oneHundredBumperLeft = new Circle(116, 328, 40), + fiftyBumper = new Circle(255, 474, 40), + fiveXBumperLeft = new Polygon(), + fiveXBumperRight = new Polygon(), + twoXBumperLeft = new Polygon(), + twoXBumperRight = new Polygon(), + oneXBumperLeft = new Polygon(), + oneXBumperRight = new Polygon(), + upperLeftBarLeft = new Polygon(), + upperLeftBarRight = new Polygon(), + upperRightBarLeft = new Polygon(), + upperRightBarRight = new Polygon(), + lowerLeftBarLeft = new Polygon(), + lowerLeftBarRight = new Polygon(), + lowerRightBarLeft = new Polygon(), + lowerRightBarRight = new Polygon(), + leftFlipperShape = new Polygon(), + leftFlipperBaselineShape = new Polygon(), + rightFlipperShape = new Polygon(), + rightFlipperBaselineShape = new Polygon(), + actuatorPlatformShape = new Polygon(), + leftBoundary = new Polygon(), + rightBoundary = new Polygon(); + +// Pause and Auto-pause....................................... + +togglePaused = function () { + game.togglePaused(); + pausedToast.style.display = game.paused ? 'inline' : 'none'; +}; + +pausedToast.onclick = function (e) { + pausedToast.style.display = 'none'; + togglePaused(); +}; + +window.onblur = function windowOnBlur() { + if (!launching && !loading && !gameOver && !game.paused) { + game.togglePaused(); + pausedToast.style.display = game.paused ? 'inline' : 'none'; + } +}; + +window.onfocus = function windowOnFocus() { + if (game.paused) { + game.togglePaused(); + pausedToast.style.display = game.paused ? 'inline' : 'none'; + } +}; + +// New game .................................................. + +newGameButton.onclick = function (e) { + gameOverToast.style.display = 'none'; + startNewGame(); +}; + +function startNewGame() { + showPolygonsOnlyToast.style.display = 'block'; + highScoreParagraph.style.display = 'none'; + gameOver = false; + livesLeft = 3; + score = 0; + showingHighScores = false; + loading = false; + actuatorSprite.visible = true; + ballSprite.visible = true; +}; + +// High Scores................................................ + +// Change game display to show high scores when +// player bests the high score. + +showHighScores = function () { + highScoreParagraph.style.display = 'inline'; + highScoreParagraph.innerText = score; + highScoreToast.style.display = 'inline'; + updateHighScoreList(); +}; + +// The game shows the list of high scores in +// an ordered list. This method creates that +// list element, and populates it with the +// current high scores. + +updateHighScoreList = function () { + var el, + highScores = game.getHighScores(), + length = highScores.length, + highScore, + listParent = highScoreList.parentNode; + + listParent.removeChild(highScoreList); + highScoreList = document.createElement('ol'); + highScoreList.id = 'highScoreList'; // So CSS takes effect + listParent.appendChild(highScoreList); + + if (length > 0) { + previousHighScoresTitle.style.display = 'block'; + + length = length > 10 ? 10 : length; + + for (var i=0; i < length; ++i) { + + highScore = highScores[i]; + el = document.createElement('li'); + el.innerText = highScore.score + + ' by ' + highScore.name; + highScoreList.appendChild(el); + } + } + else { + previousHighScoresTitle.style.display = 'none'; + } +} + +// The browser invokes this method when the user clicks on the +// Add My Score button. + +addMyScoreButton.onclick = function (e) { + game.setHighScore({ name: nameInput.value, score: lastScore }); + updateHighScoreList(); + addMyScoreButton.disabled = 'true'; + nameInput.value = ''; +}; + + +// The browser invokes this method when the user clicks on the +// new game button. + +newGameFromHighScoresButton.onclick = function (e) { + highScoreToast.style.display = 'none'; + startNewGame(); +}; + +// The Add My Score button is only enabled when there +// is something in the nameInput field. + +nameInput.onkeyup = function (e) { + if (nameInput.value.length > 0) { + addMyScoreButton.disabled = false; + } + else { + addMyScoreButton.disabled = true; + } +}; + + +var bumperLit = undefined; +var interval = undefined; + +// Score Display.............................................. + +updateScore = function (shape) { + if (shape && !loading && game.lastScoreUpdate !== undefined) { + //if (game.gameTime - game.lastScoreUpdate > 500) { + if (shape === fiveHundredBumper) score += 500; + else if (shape === oneHundredBumperLeft) score += 100; + else if (shape === oneHundredBumperRight) score += 100; + else if (shape === fiftyBumper) score += 50; + + scoreToast.style.display = 'inline'; + scoreToast.innerHTML = score.toFixed(0); + game.lastScoreUpdate = game.gameTime; + //} + } + else { + game.lastScoreUpdate = game.gameTime; + } +}; + +// Collision Detection........................................ + +function drawCollisionShapes() { + var centroid; + + shapes.forEach( function (shape) { + shape.stroke(game.context); + game.context.beginPath(); + centroid = shape.centroid(); + game.context.arc(centroid.x, centroid.y, 1.5, 0, Math.PI*2, false); + game.context.stroke(); + }); +} + +function clampBallVelocity() { + if (ballSprite.velocityX > MAX_BALL_VELOCITY) + ballSprite.velocityX = MAX_BALL_VELOCITY; + else if (ballSprite.velocityX < -MAX_BALL_VELOCITY) + ballSprite.velocityX = -MAX_BALL_VELOCITY; + + if(ballSprite.velocityY > MAX_BALL_VELOCITY) + ballSprite.velocityY = MAX_BALL_VELOCITY; + else if (ballSprite.velocityY < -MAX_BALL_VELOCITY) + ballSprite.velocityY = -MAX_BALL_VELOCITY; +}; + +function separate(mtv) { + var dx, dy, velocityMagnitude, point, theta=0, + velocityVector = new Vector(new Point(ballSprite.velocityX, ballSprite.velocityY)), + velocityUnitVector = velocityVector.normalize(); + + if (mtv.axis.x === 0) { + theta = Math.PI/2; + } + else { + theta = Math.atan(mtv.axis.y / mtv.axis.x); + } + + dy = mtv.overlap * Math.sin(theta); + dx = mtv.overlap * Math.cos(theta); + + if (mtv.axis.x < 0 && dx > 0 || mtv.axis.x > 0 && dx < 0) dx = -dx; // account for negative angle + if (mtv.axis.y < 0 && dy > 0 || mtv.axis.y > 0 && dy < 0) dy = -dy; + + ballSprite.left += dx; + ballSprite.top += dy; +} + +function checkMTVAxisDirection(mtv, shape) { + var centroid1, centroid2, centroidVector, centroidUnitVector, flipOrNot; + centroid1 = new Vector(ballShape.centroid()); + centroid2 = new Vector(shape.centroid()), + centroidVector = centroid2.subtract(centroid1), + centroidUnitVector = (new Vector(centroidVector)).normalize(); + + if (mtv.axis === undefined) + return; + + if (centroidUnitVector.dotProduct(mtv.axis) > 0) { + mtv.axis.x = -mtv.axis.x; + mtv.axis.y = -mtv.axis.y; + } +}; + +function bounce(mtv, shape, bounceCoefficient) { + var velocityVector = new Vector(new Point(ballSprite.velocityX, ballSprite.velocityY)), + velocityUnitVector = velocityVector.normalize(), + velocityVectorMagnitude = velocityVector.getMagnitude(), + reflectAxis, point; + + checkMTVAxisDirection(mtv, shape); + + if (!loading && !game.paused) { + if (mtv.axis !== undefined) { + reflectAxis = mtv.axis.perpendicular(); + } + + separate(mtv); + + point = velocityUnitVector.reflect(reflectAxis); + + if (shape === leftFlipperShape || shape === rightFlipperShape) { + if (velocityVectorMagnitude < MIN_BALL_VELOCITY_OFF_FLIPPERS) + velocityVectorMagnitude = MIN_BALL_VELOCITY_OFF_FLIPPERS; + } + + ballSprite.velocityX = point.x * velocityVectorMagnitude * bounceCoefficient; + ballSprite.velocityY = point.y * velocityVectorMagnitude * bounceCoefficient; + + clampBallVelocity(); + } +} + + +function collisionDetected(mtv) { + return mtv.axis !== undefined && mtv.overlap !== 0; +}; + +function detectCollisions() { + var mtv, shape, displacement, position, lastPosition; + + if (!launching && !loading && !game.paused) { + ballShape.x = ballSprite.left; + ballShape.y = ballSprite.top; + ballShape.points = []; + ballShape.setPolygonPoints(); + + position = new Vector(new Point(ballSprite.left, ballSprite.top)); + lastPosition = new Vector(new Point(lastBallPosition.x, lastBallPosition.y)); + displacement = position.subtract(lastPosition); + + for (var i=0; i < shapes.length; ++i) { + shape = shapes[i]; + + if (shape !== ballShape) { + mtv = ballShape.collidesWith(shape, displacement); + if (collisionDetected(mtv)) { + updateScore(shape); + + setTimeout ( function (e) { + bumperLit = undefined; + }, 100); + + if (shape === twoXBumperLeft || + shape === twoXBumperRight || + shape === fiveXBumperRight || + shape === fiveXBumperLeft || + shape === fiftyBumper || + shape === oneHundredBumperLeft || + shape === oneHundredBumperRight || + shape === fiveHundredBumper) { + game.playSound('bumper'); + bounce(mtv, shape, 4.5); + bumperLit = shape; + return true; + + } + else if (shape === rightFlipperShape) { + if (rightFlipperAngle === 0) { + bounce(mtv, shape, 1 + rightFlipperAngle); + return true; + } + } + else if (shape === leftFlipperShape) { + if (leftFlipperAngle === 0) { + bounce(mtv, shape, 1 + leftFlipperAngle); + return true; + } + } + else if (shape === actuatorPlatformShape) { + bounce(mtv, shape, 0.2); + return true; + } + else { + bounce(mtv, shape, 0.96); + return true; + } + } + } + } + + flipperCollisionDetected = false; + + detectFlipperCollision(LEFT_FLIPPER); + detectFlipperCollision(RIGHT_FLIPPER); + + return flipperCollisionDetected; + } + return false; +} + +function detectFlipperCollision(flipper) { + var v1, v2, l1, l2, surface, ip, bbox = {}, riseTimer; + + bbox.top = 725; + bbox.bottom = 850; + + if (flipper === LEFT_FLIPPER) { + v1 = new Vector(leftFlipperBaselineShape.points[0].rotate( + LEFT_FLIPPER_ROTATION_POINT, + leftFlipperAngle)); + + v2 = new Vector(leftFlipperBaselineShape.points[1].rotate( + LEFT_FLIPPER_ROTATION_POINT, + leftFlipperAngle)); + + bbox.left = 170; + bbox.right = 265; + riseTimer = leftFlipperRiseTimer; + } + else if (flipper === RIGHT_FLIPPER) { + v1 = new Vector(rightFlipperBaselineShape.points[0].rotate( + RIGHT_FLIPPER_ROTATION_POINT, + rightFlipperAngle)); + + v2 = new Vector(rightFlipperBaselineShape.points[1].rotate( + RIGHT_FLIPPER_ROTATION_POINT, + rightFlipperAngle)); + + bbox.left = 245; + bbox.right = 400; + riseTimer = rightFlipperRiseTimer; + } + + if ( ! flipperCollisionDetected && riseTimer.isRunning() && + ballSprite.top + ballSprite.height > bbox.top && ballSprite.left < bbox.right) { + + surface = v2.subtract(v1); + l1 = new Line(new Point(ballSprite.left, ballSprite.top), lastBallPosition), + l2 = new Line(new Point(v2.x, v2.y), new Point(v1.x, v1.y)), + ip = l1.intersectionPoint(l2); + + if (ip.x > bbox.left && ip.x < bbox.right) { + reflectVelocityAroundVector(surface.perpendicular()); + + ballSprite.velocityX = ballSprite.velocityX * 3.5; + ballSprite.velocityY = ballSprite.velocityY * 3.5; + + if (ballSprite.velocityY > 0) + ballSprite.velocityY = -ballSprite.velocityY; + + if (flipper === LEFT_FLIPPER && ballSprite.velocityX < 0) + ballSprite.velocityX = -ballSprite.velocityX; + + else if (flipper === RIGHT_FLIPPER && ballSprite.velocityX > 0) + ballSprite.velocityX = -ballSprite.velocityX; + } + } +} + +function reflectVelocityAroundVector(v) { + var velocityVector = new Vector(new Point(ballSprite.velocityX, ballSprite.velocityY)), + velocityUnitVector = velocityVector.normalize(), + velocityVectorMagnitude = velocityVector.getMagnitude(), + point = velocityUnitVector.reflect(v); + + ballSprite.velocityX = point.x * velocityVectorMagnitude; + ballSprite.velocityY = point.y * velocityVectorMagnitude; +} + +// Game Loop.................................................. + +function showTryAgainImage() { + game.context.save(); + game.context.arc(TRY_AGAIN_X, TRY_AGAIN_Y, TRY_AGAIN_RADIUS, + 0, Math.PI*2, false); + + game.context.clip(); + + game.context.drawImage(game.getImage('images/tryAgain.png'), 0, + game.context.canvas.height-200); + game.context.restore(); +}; + +function drawExtraBall(index) { + game.context.drawImage(game.getImage('images/ball.png'), + EXTRA_BALLS_RIGHT - EXTRA_BALL_WIDTH*index, + EXTRA_BALLS_BOTTOM); +}; + +function over() { + var highScore; + highScores = game.getHighScores(); + + if (highScores.length == 0 || score > highScores[0].score) { + showingHighScores = true; + actuatorSprite.visible = false; + ballSprite.visible = false; + showHighScores(); + } + else { + gameOverToast.style.display = 'inline'; + } + + gameOver = true; + lastScore = score; + score = 0; +}; + +var FIVE_HUNDRED_BUMPER_LEFT = 216, + FIVE_HUNDRED_BUMPER_RIGHT = 147, + ONE_HUNDRED_BUMPER_LEFT = 77, + ONE_HUNDRED_BUMPER_RIGHT = 288; + +function drawLitBumper() { + if (bumperLit === fiveHundredBumper) { + game.context.drawImage(game.getImage('images/fiveHundredBumperBright.png'), + FIVE_HUNDRED_BUMPER_LEFT, + FIVE_HUNDRED_BUMPER_RIGHT); + } + else if (bumperLit === oneHundredBumperLeft) { + game.context.drawImage(game.getImage('images/oneHundredBumperBright.png'), + ONE_HUNDRED_BUMPER_LEFT, + ONE_HUNDRED_BUMPER_RIGHT); + } + else if (bumperLit === oneHundredBumperRight) { + game.context.drawImage(game.getImage('images/oneHundredBumperBright.png'),355,288); + } + else if (bumperLit === fiftyBumper) { + game.context.drawImage(game.getImage('images/fiftyBumperBright.png'),215,434); + } + else if (bumperLit === oneXBumperLeft) { + game.context.drawImage(game.getImage('images/oneXBumperLeftBright.png'),71,776); + } + else if (bumperLit === oneXBumperRight) { + game.context.drawImage(game.getImage('images/oneXBumperRightBright.png'),305,775); + } + else if (bumperLit === twoXBumperLeft) { + game.context.drawImage(game.getImage('images/twoXBumperLeftBright.png'), 93, 632); + } + else if (bumperLit === twoXBumperRight) { + game.context.drawImage(game.getImage('images/twoXBumperRightBright.png'),333,631); + } + else if (bumperLit === fiveXBumperLeft) { + game.context.drawImage(game.getImage('images/fiveXBumperLeftBright.png'),95,450); + } + else if (bumperLit === fiveXBumperRight) { + game.context.drawImage(game.getImage('images/fiveXBumperRightBright.png'),350,450); + } +} + +game.startAnimate = function (time) { + var collisionOccurred; + + if (loading || game.paused || launching) + return; + + + if (ballOutOfPlay) { + ballOutOfPlay = false; + prepareForLaunch(); + brieflyShowTryAgainImage(2000); + livesLeft--; + + if (!gameOver && livesLeft === 0) { + over(); + } + return; + } + + adjustRightFlipperCollisionPolygon(); + adjustLeftFlipperCollisionPolygon(); + + collisionOccurred = detectCollisions(); + + if (!collisionOccurred && applyGravityAndFriction) { + applyFrictionAndGravity(parseFloat(time - game.lastTime)); // modifies ball velocity + } +}; + +game.paintUnderSprites = function () { + if (loading) + return; + + updateLeftFlipper(); + updateRightFlipper(); + + if (showPolygonsOnly) { + drawCollisionShapes(); + } + else { + if (!showingHighScores) { + game.context.drawImage(game.getImage('images/background.png'),0,0); + + drawLitBumper(); + + if (showTryAgain) { + showTryAgainImage(); + } + + paintLeftFlipper(); + paintRightFlipper(); + + for (var i=0; i < livesLeft-1; ++i) { + drawExtraBall(i); + } + } + } +}; + + +var fiveHundredBumper = new Circle(256, 187, 40); +var oneHundredBumperRight = new Circle(395, 328, 40); +var oneHundredBumperLeft = new Circle(116, 328, 40); +var fiftyBumper = new Circle(255, 474, 40); + +//rightFlipperImage.src = 'images/rightFlipper.png'; +//leftFlipperImage.src = 'images/leftFlipper.png'; +//fiveHundredBumperBrightImage.src = 'images/fiveHundredBumper-bright.png'; +//oneHundredBumperBrightImage.src = 'images/oneHundredBumper-bright.png'; +//fiftyBumperBrightImage.src = 'images/fiftyBumper-bright.png'; +//oneXBumperLeftBrightImage.src = 'images/oneXBumperLeft-bright.png'; +//oneXBumperRightBrightImage.src = 'images/oneXBumperRight-bright.png'; +//twoXBumperRightBrightImage.src = 'images/twoXBumperRight-bright.png'; +//twoXBumperLeftBrightImage.src = 'images/twoXBumperLeft-bright.png'; +//fiveXBumperRightBrightImage.src = 'images/fiveXBumperRight-bright.png'; +//fiveXBumperLeftBrightImage.src = 'images/fiveXBumperLeft-bright.png'; + +var LEFT_FLIPPER_ROTATION_POINT = new Point(145, 775), + RIGHT_FLIPPER_ROTATION_POINT = new Point(370, 775); + +function adjustLeftFlipperCollisionPolygon() { + if(leftFlipperRiseTimer.isRunning() || leftFlipperFallTimer.isRunning()) { + for (var i=0; i < leftFlipperShape.points.length; ++i) { + var rp = leftFlipperBaselineShape.points[i].rotate( + LEFT_FLIPPER_ROTATION_POINT, + leftFlipperAngle); + + leftFlipperShape.points[i].x = rp.x; + leftFlipperShape.points[i].y = rp.y; + } + } +} + +function adjustRightFlipperCollisionPolygon() { + if(rightFlipperRiseTimer.isRunning() || rightFlipperFallTimer.isRunning()) { + for (var i=0; i < rightFlipperShape.points.length; ++i) { + var rp = rightFlipperBaselineShape.points[i].rotate( + RIGHT_FLIPPER_ROTATION_POINT, + -rightFlipperAngle); + + rightFlipperShape.points[i].x = rp.x; + rightFlipperShape.points[i].y = rp.y; + } + } +} + +function resetLeftFlipperCollisionPolygon() { + for (var i=0; i < leftFlipperShape.points.length; ++i) { + var point = leftFlipperBaselineShape.points[i]; + + leftFlipperShape.points[i].x = leftFlipperBaselineShape.points[i].x; + leftFlipperShape.points[i].y = leftFlipperBaselineShape.points[i].y; + } +} + +function resetRightFlipperCollisionPolygon() { + for (var i=0; i < rightFlipperShape.points.length; ++i) { + var point = rightFlipperBaselineShape.points[i]; + + rightFlipperShape.points[i].x = rightFlipperBaselineShape.points[i].x; + rightFlipperShape.points[i].y = rightFlipperBaselineShape.points[i].y; + } +} + +function updateLeftFlipper() { + if (leftFlipperRiseTimer.isRunning()) { // Flipper is rising + if (leftFlipperRiseTimer.isOver()) { // Finished rising + leftFlipperRiseTimer.stop(); // Stop rise timer + leftFlipperAngle = MAX_FLIPPER_ANGLE; // Set flipper angle + leftFlipperFallTimer.start(); // Start falling + } + else { // Flipper is still rising + leftFlipperAngle = + MAX_FLIPPER_ANGLE/FLIPPER_RISE_DURATION * + leftFlipperRiseTimer.getElapsedTime(); + } + } + else if (leftFlipperFallTimer.isRunning()) { // Left flipper is falling + if (leftFlipperFallTimer.isOver()) { // Finished falling + leftFlipperFallTimer.stop(); // Stop fall timer + leftFlipperAngle = 0; // Set flipper angle + resetLeftFlipperCollisionPolygon(); // Reset collision polygon + } + else { // Flipper is still falling + leftFlipperAngle = MAX_FLIPPER_ANGLE - + MAX_FLIPPER_ANGLE/FLIPPER_FALL_DURATION * + leftFlipperFallTimer.getElapsedTime(); + } + } +} + +function paintLeftFlipper() { + if (leftFlipperRiseTimer.isRunning() || leftFlipperFallTimer.isRunning()) { + game.context.save(); + game.context.translate(LEFT_FLIPPER_PIVOT_X, LEFT_FLIPPER_PIVOT_Y); + game.context.rotate(-leftFlipperAngle); + game.context.drawImage(game.getImage('images/leftFlipper.png'), + -LEFT_FLIPPER_PIVOT_OFFSET_X, + -LEFT_FLIPPER_PIVOT_OFFSET_Y); + game.context.restore(); + } + else { + game.context.drawImage(game.getImage('images/leftFlipper.png'), + LEFT_FLIPPER_PIVOT_X - LEFT_FLIPPER_PIVOT_OFFSET_X, + LEFT_FLIPPER_PIVOT_Y - LEFT_FLIPPER_PIVOT_OFFSET_Y); + } +} +function paintRightFlipper() { + if (rightFlipperRiseTimer.isRunning() || rightFlipperFallTimer.isRunning()) { + game.context.save(); + game.context.translate(370,776); + game.context.rotate(rightFlipperAngle); + game.context.drawImage(game.getImage('images/rightFlipper.png'),-99,-29); + game.context.restore(); + } + else { + game.context.drawImage(game.getImage('images/rightFlipper.png'),272,745); + } +} + +function updateRightFlipper() { + if (rightFlipperRiseTimer.isRunning()) { + if (rightFlipperRiseTimer.isOver()) { + rightFlipperRiseTimer.stop(); + flipperCollisionDetected = false; // reset + + rightFlipperFallTimer.start(); + rightFlipperAngle = MAX_FLIPPER_ANGLE; + } + else { + rightFlipperAngle = + MAX_FLIPPER_ANGLE/FLIPPER_RISE_DURATION * + rightFlipperRiseTimer.getElapsedTime(); + } + } + else if (rightFlipperFallTimer.isRunning()) { + rightFlipperAngle = MAX_FLIPPER_ANGLE - + MAX_FLIPPER_ANGLE/FLIPPER_FALL_DURATION * + rightFlipperFallTimer.getElapsedTime(); + + if (rightFlipperFallTimer.isOver()) { + rightFlipperFallTimer.stop(); + rightFlipperAngle = 0; + resetRightFlipperCollisionPolygon(); + } + } +} + +function adjustActuatorPlatformShape() { + var i, point; + + for (i=0; i < actuatorPlatformShape.points.length; ++i) { + point = actuatorPlatformShape.points[i]; + if ( i < 2 || i === actuatorPlatformShape.points.length-1) + point.y = ACTUATOR_TOP + launchStep*10; + else + point.y = ACTUATOR_TOP + launchStep*10 + 10; + } +} + +// Key Listeners.............................................. + +lastKeyListenerTime = 0, // For throttling arrow keys + +game.addKeyListener( + { + key: 'k', + listener: function () { + if ( !launching && !gameOver) { + rightFlipperRiseTimer.start(); + rightFlipperAngle = 0; + game.playSound('flipper'); + } + } + } +); + +game.addKeyListener( + { + key: 'd', + listener: function () { + if ( !launching && !gameOver) { + leftFlipperRiseTimer.start(); + leftFlipperAngle = 0; + game.playSound('flipper'); + } + } + } +); + +game.addKeyListener( + { + key: 'p', + listener: function () { + togglePaused(); + } + } +); + +game.addKeyListener( + { + key: 'up arrow', + listener: function () { + var now; + + if (!launching || launchStep === 1) + return; + + now = +new Date(); + if (now - lastKeyListenerTime > 80) { // throttle + lastKeyListenerTime = now; + launchStep--; + actuatorSprite.painter.image = launchImages[launchStep-1]; + ballSprite.top = BALL_LAUNCH_TOP + (launchStep-1) * 9; + adjustActuatorPlatformShape(); + } + } + } +); + +game.addKeyListener( + { + key: 'down arrow', + listener: function () { + var now; + + if (!launching || launchStep === LAUNCH_STEPS) + return; + + now = +new Date(); + if (now - lastKeyListenerTime > 80) { // throttle + lastKeyListenerTime = now; + launchStep++; + actuatorSprite.painter.image = launchImages[launchStep-1]; + ballSprite.top = BALL_LAUNCH_TOP + (launchStep-1) * 9; + adjustActuatorPlatformShape(); + } + } + } +); + +function adjustRightBoundaryAfterLostBall() { + rightBoundary.points[1].x = 508; +} + +function adjustRightBoundaryAfterLaunch() { + rightBoundary.points[1].x = 460; +} + +game.addKeyListener( + { + key: 'space', + listener: function () { + if (!launching && ballSprite.left === BALL_LAUNCH_LEFT && + ballSprite.velocityY === 0) { + launching = true; + ballSprite.velocityY = 0; + applyGravityAndFriction = false; + launchStep = 1; + } + if (launching) { + ballSprite.velocityY = -300 * launchStep; + launching = false; + launchStep = 1; + + setTimeout( function (e) { + actuatorSprite.painter.image = launchImages[0]; + adjustActuatorPlatformShape(); + }, 50); + + setTimeout( function (e) { + applyGravityAndFriction = true; + adjustRightBoundaryAfterLaunch(); + }, 2000); + } + } + } +); + +game.addKeyListener( + { + key: 'right arrow', + listener: function () { + var now = +new Date(); + if (now - lastKeyListenerTime > 200) { // throttle + lastKeyListenerTime = now; + } + } + } +); + +game.addKeyListener( + { + key: 'left arrow', + listener: function () { + var now = +new Date(); + if (now - lastKeyListenerTime > 200) { // throttle + lastKeyListenerTime = now; + } + } + } +); + +// Clear high scores checkbox................................. + +clearHighScoresCheckbox.onclick = function (e) { + if (clearHighScoresCheckbox.checked) { + game.clearHighScores(); + } +}; + +// Load game.................................................. + +loading = true; + var interval, + percentComplete = 0; + + progressDiv.style.display = 'block'; + progressDiv.appendChild(progressbar.domElement); + +// Start game................................................. + +//progressDiv.style.display = 'none'; +//loadingToast.style.display = 'none'; + +ballSprite.top = BALL_LAUNCH_TOP; +ballSprite.left = BALL_LAUNCH_LEFT; +ballSprite.width = 33; +ballSprite.height = ballSprite.width; + + +leftBoundary.points.push(new Point(45, 235)); +leftBoundary.points.push(new Point(45, game.context.canvas.height)); +leftBoundary.points.push(new Point(-450, game.context.canvas.height)); +leftBoundary.points.push(new Point(-450, 235)); +leftBoundary.points.push(new Point(45, 235)); + +rightBoundary.points.push(new Point(508, 235)); +rightBoundary.points.push(new Point(508, game.context.canvas.height)); +rightBoundary.points.push(new Point(508*2, game.context.canvas.height)); +rightBoundary.points.push(new Point(508*2, 235)) +rightBoundary.points.push(new Point(508, 235)); + +actuatorPlatformShape.points.push(new Point(ACTUATOR_LEFT-5, ACTUATOR_TOP)); +actuatorPlatformShape.points.push(new Point(ACTUATOR_LEFT-5 + ACTUATOR_PLATFORM_WIDTH, + ACTUATOR_TOP)); + +actuatorPlatformShape.points.push(new Point(ACTUATOR_LEFT-5 + ACTUATOR_PLATFORM_WIDTH, + ACTUATOR_TOP + ACTUATOR_PLATFORM_HEIGHT)); + +actuatorPlatformShape.points.push(new Point(ACTUATOR_LEFT-5, + ACTUATOR_TOP + ACTUATOR_PLATFORM_HEIGHT)); + +actuatorPlatformShape.points.push(new Point(ACTUATOR_LEFT-5, ACTUATOR_TOP)); + +rightFlipperShape.points.push(new Point(365, 745)); +rightFlipperShape.points.push(new Point(272, 836)); +rightFlipperShape.points.push(new Point(293, 857)); +rightFlipperShape.points.push(new Point(398, 781)); +rightFlipperShape.points.push(new Point(365, 745)); + +leftFlipperShape.points.push(new Point(142, 743)); +leftFlipperShape.points.push(new Point(239, 837)); +leftFlipperShape.points.push(new Point(218, 855)); +leftFlipperShape.points.push(new Point(116, 783)); +leftFlipperShape.points.push(new Point(142, 743)); + +rightFlipperBaselineShape.points.push(new Point(365, 745)); +rightFlipperBaselineShape.points.push(new Point(272, 836)); +rightFlipperBaselineShape.points.push(new Point(293, 857)); +rightFlipperBaselineShape.points.push(new Point(398, 781)); +rightFlipperBaselineShape.points.push(new Point(365, 745)); + +leftFlipperBaselineShape.points.push(new Point(142, 743)); +leftFlipperBaselineShape.points.push(new Point(239, 837)); +leftFlipperBaselineShape.points.push(new Point(218, 855)); +leftFlipperBaselineShape.points.push(new Point(116, 783)); +leftFlipperBaselineShape.points.push(new Point(142, 743)); + +lowerRightBarLeft.points.push(new Point(294,525)); +lowerRightBarLeft.points.push(new Point(306,525)); +lowerRightBarLeft.points.push(new Point(306,590)); +lowerRightBarLeft.points.push(new Point(294,590)); +lowerRightBarLeft.points.push(new Point(294,525)); + +lowerRightBarRight.points.push(new Point(342,525)); +lowerRightBarRight.points.push(new Point(354,525)); +lowerRightBarRight.points.push(new Point(354,590)); +lowerRightBarRight.points.push(new Point(342,590)); +lowerRightBarRight.points.push(new Point(342,525)); + +lowerLeftBarLeft.points.push(new Point(156,525)); +lowerLeftBarLeft.points.push(new Point(168,525)); +lowerLeftBarLeft.points.push(new Point(168,590)); +lowerLeftBarLeft.points.push(new Point(156,590)); +lowerLeftBarLeft.points.push(new Point(156,525)); + +lowerLeftBarRight.points.push(new Point(204,525)); +lowerLeftBarRight.points.push(new Point(216,525)); +lowerLeftBarRight.points.push(new Point(216,590)); +lowerLeftBarRight.points.push(new Point(204,590)); +lowerLeftBarRight.points.push(new Point(204,525)); + +upperLeftBarLeft.points.push(new Point(86,185)); +upperLeftBarLeft.points.push(new Point(86,263)); +upperLeftBarLeft.points.push(new Point(98,263)); +upperLeftBarLeft.points.push(new Point(98,185)); +upperLeftBarLeft.points.push(new Point(86,185)); + +upperLeftBarRight.points.push(new Point(134,185)); +upperLeftBarRight.points.push(new Point(136,263)); +upperLeftBarRight.points.push(new Point(146,263)); +upperLeftBarRight.points.push(new Point(146,185)); +upperLeftBarRight.points.push(new Point(134,185)); + +upperRightBarLeft.points.push(new Point(368,185)); +upperRightBarLeft.points.push(new Point(368,263)); +upperRightBarLeft.points.push(new Point(380,263)); +upperRightBarLeft.points.push(new Point(380,185)); +upperRightBarLeft.points.push(new Point(368,185)); + +upperRightBarRight.points.push(new Point(417,185)); +upperRightBarRight.points.push(new Point(417,263)); +upperRightBarRight.points.push(new Point(427,263)); +upperRightBarRight.points.push(new Point(427,185)); +upperRightBarRight.points.push(new Point(417,185)); + +oneXBumperLeft.points.push(new Point(80,780)); +oneXBumperLeft.points.push(new Point(215,875)); +oneXBumperLeft.points.push(new Point(80,875)); +oneXBumperLeft.points.push(new Point(80,780)); + +oneXBumperRight.points.push(new Point(300,875)); +oneXBumperRight.points.push(new Point(435,775)); +oneXBumperRight.points.push(new Point(435,875)); +oneXBumperRight.points.push(new Point(300,875)); + +twoXBumperLeft.points.push(new Point(98,635)); +twoXBumperLeft.points.push(new Point(180,715)); +twoXBumperLeft.points.push(new Point(98,715)); +twoXBumperLeft.points.push(new Point(98,635)); + +twoXBumperRight.points.push(new Point(420,630)); +twoXBumperRight.points.push(new Point(420,715)); +twoXBumperRight.points.push(new Point(330,715)); +twoXBumperRight.points.push(new Point(420,630)); + +fiveXBumperLeft.points.push(new Point(98,450)); +fiveXBumperLeft.points.push(new Point(163,450)); +fiveXBumperLeft.points.push(new Point(98,505)); +fiveXBumperLeft.points.push(new Point(98,450)); + +fiveXBumperRight.points.push(new Point(350,450)); +fiveXBumperRight.points.push(new Point(415,450)); +fiveXBumperRight.points.push(new Point(415,505)); +fiveXBumperRight.points.push(new Point(350,450)); + +shapes.push(ballShape); +shapes.push(leftBoundary); +shapes.push(rightBoundary); + +shapes.push(fiveHundredBumper); +shapes.push(oneHundredBumperLeft); +shapes.push(oneHundredBumperRight); +shapes.push(fiftyBumper); +shapes.push(fiveXBumperLeft); +shapes.push(fiveXBumperRight); +shapes.push(twoXBumperLeft); +shapes.push(twoXBumperRight); +shapes.push(upperLeftBarLeft); +shapes.push(upperLeftBarRight); +shapes.push(upperRightBarLeft); +shapes.push(upperRightBarRight); +//shapes.push(oneXBumperLeft); +//shapes.push(oneXBumperRight); +shapes.push(lowerLeftBarLeft); +shapes.push(lowerLeftBarRight); +shapes.push(lowerRightBarLeft); +shapes.push(lowerRightBarRight); + +shapes.push(rightFlipperShape); +shapes.push(leftFlipperShape); + +shapes.push(actuatorPlatformShape); + +ballSprite.velocityX = 0; +ballSprite.velocityY = 0; +ballSprite.visible = false; + +actuatorSprite.velocityX = 0; +actuatorSprite.velocityY = 0; +actuatorSprite.width = 60; +actuatorSprite.height = 100; +actuatorSprite.visible = true; + +game.addSprite(actuatorSprite); +game.addSprite(ballSprite); + +function windowToCanvas(e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + bbox = game.context.canvas.getBoundingClientRect(); + + return { x: x - bbox.left * (game.context.canvas.width / bbox.width), + y: y - bbox.top * (game.context.canvas.height / bbox.height) + }; +} + +function drawHorizontalLine (y) { + game.context.moveTo(0,y+0.5); + game.context.lineTo(game.context.canvas.width,y+0.5); + game.context.stroke(); +} + +function drawVerticalLine (x) { + game.context.moveTo(x+0.5,0); + game.context.lineTo(x+0.5,game.context.canvas.height); + game.context.stroke(); +} + +showPolygonsOnlyCheckbox.onclick = function (e) { + showPolygonsOnly = showPolygonsOnlyCheckbox.checked; + if (showPolygonsOnly) { + ballSprite.visible = false; + actuatorSprite.visible = false; + } + else { + ballSprite.visible = true; + actuatorSprite.visible = true; + } +}; + +actuatorSprite.top = ACTUATOR_TOP, +actuatorSprite.left = ACTUATOR_LEFT, +actuatorSprite.visible = false; + +function createDomePolygons(centerX, centerY, radius, sides) { + var polygon, + polygons = [], + startTheta = 0, + endTheta, + midPointTheta, + thetaDelta = Math.PI/sides, + midPointRadius = radius*1.5; + + for (var i=0; i < sides; ++i) { + polygon = new Polygon(); + + endTheta = startTheta + thetaDelta; + midPointTheta = startTheta + (endTheta - startTheta)/2; + + polygon.points.push( + new Point(centerX + radius * Math.cos(startTheta), + centerY - radius * Math.sin(startTheta))); + + polygon.points.push( + new Point(centerX + midPointRadius * Math.cos(midPointTheta), + centerY - midPointRadius * Math.sin(midPointTheta))); + + polygon.points.push( + new Point(centerX + radius * Math.cos(endTheta), + centerY - radius * Math.sin(endTheta))); + + polygon.points.push( + new Point(centerX + radius * Math.cos(startTheta), + centerY - radius * Math.sin(startTheta))); + + polygons.push(polygon); + + startTheta += thetaDelta; + } + return polygons; +} + +var DOME_SIDES = 15, + DOME_X = 275, + DOME_Y = 235, + DOME_RADIUS = 232, + domePolygons = createDomePolygons(DOME_X, DOME_Y, DOME_RADIUS, DOME_SIDES); + +domePolygons.forEach( function (polygon) { + shapes.push(polygon); +}); + +if (showPolygonsOnly) + actuatorSprite.visible = false; + +rightFlipperShape.centroid = function () { + return new Point(450, 930); +}; + +leftFlipperShape.centroid = function () { + return new Point(60, 930); +}; + +showingHighScores = false; + +game.queueImage('images/rightFlipper.png'); +game.queueImage('images/leftFlipper.png'); +game.queueImage('images/ball.png'); +game.queueImage('images/tryAgain.png'); + +game.queueImage('images/fiftyBumperBright.png'); +game.queueImage('images/oneHundredBumperBright.png'); +game.queueImage('images/fiveHundredBumperBright.png'); + +game.queueImage('images/oneXBumperLeftBright.png'); +game.queueImage('images/oneXBumperRightBright.png'); + +game.queueImage('images/twoXBumperRightBright.png'); +game.queueImage('images/twoXBumperLeftBright.png'); + +game.queueImage('images/fiveXBumperRightBright.png'); +game.queueImage('images/fiveXBumperLeftBright.png'); +game.queueImage('images/tryAgain.png'); +game.queueImage('images/background.png'); + +for (var i=0; i < LAUNCH_STEPS; ++i) { + game.queueImage('images/actuator-' + i + '.png'); +} + + +var interval = setInterval( function (e) { + var percentComplete = game.loadImages(); + + progressbar.draw(percentComplete); + + if (percentComplete >= 100) { + clearInterval(interval); + + progressDiv.style.display = 'none'; + loadingToast.style.display = 'none'; + + showPolygonsOnlyToast.style.display = 'block'; + showPolygonsOnlyToast.style.left = '290px'; + scoreToast.style.display = 'inline'; + + launching = true; + loading = false; + + score = 0; + scoreToast.innerText = '0'; // won't get set till later, otherwise + + ballSprite.visible = true; + actuatorSprite.visible = true; + //game.playSound('pinball'); + + for (var i=0; i < LAUNCH_STEPS; ++i) { + launchImages[i] = new Image(); + launchImages[i].src = 'images/actuator-' + i + '.png'; + } + game.start(); + } +}, 16); diff --git a/canvas/ch09/pinball/progressbar.js b/canvas/ch09/pinball/progressbar.js new file mode 100644 index 0000000..f4d00d3 --- /dev/null +++ b/canvas/ch09/pinball/progressbar.js @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {} + +COREHTML5.Progressbar = function(w, h, strokeStyle, red, green, blue) { + this.domElement = document.createElement('div'); + this.context = document.createElement('canvas').getContext('2d'); + this.domElement.appendChild(this.context.canvas); + + this.context.canvas.width = w + h; // On each end, corner radius = h/2 + this.context.canvas.height = h; + + this.setProgressbarProperties(w, h); + + this.background.globalAlpha = 0.3; + this.drawToBuffer(this.background, strokeStyle, red, green, blue); + this.drawToBuffer(this.foreground, strokeStyle, red, green, blue); + + this.percentComplete = 0; + return this; +} + +COREHTML5.Progressbar.prototype = { + LEFT: 0, + TOP: 0, + + setProgressbarProperties: function(w, h) { + this.w = w; + this.h = h; + this.cornerRadius = this.h/2, + this.right = this.LEFT + this.cornerRadius + this.w + this.cornerRadius, + this.bottom = this.TOP + this.h; + + this.background = document.createElement('canvas').getContext('2d'), + this.foreground = document.createElement('canvas').getContext('2d'), + + this.background.canvas.width = w + h; // On each end, corner radius = h/2 + this.background.canvas.height = h; + + this.foreground.canvas.width = w + h; // On each end, corner radius = h/2 + this.foreground.canvas.height = h; + }, + + draw: function (percentComplete) { + this.erase(); + this.context.drawImage(this.background.canvas, 0, 0); + + if (percentComplete > 0) { + this.context.drawImage(this.foreground.canvas, 0, 0, + this.foreground.canvas.width*(percentComplete/100), + this.foreground.canvas.height, + 0, 0, + this.foreground.canvas.width*(percentComplete/100), + this.foreground.canvas.height); + } + }, + + drawToBuffer: function (context, strokeStyle, red, green, blue) { + context.save(); + + context.fillStyle = 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + context.strokeStyle = strokeStyle; + + context.beginPath(); + + context.moveTo(this.LEFT + this.cornerRadius, this.TOP); + context.lineTo(this.right - this.cornerRadius, this.TOP); + + context.arc(this.right - this.cornerRadius, + this.TOP + this.cornerRadius, this.cornerRadius, -Math.PI/2, Math.PI/2); + + context.lineTo(this.LEFT + this.cornerRadius, + this.TOP + this.cornerRadius*2); + + context.arc(this.LEFT + this.cornerRadius, + this.TOP + this.cornerRadius, this.cornerRadius, Math.PI/2, -Math.PI/2); + + context.fill(); + + context.shadowColor = undefined; + + var gradient = context.createLinearGradient(this.LEFT, this.TOP, this.LEFT, this.bottom); + gradient.addColorStop(0, 'rgba(255,255,255,0.4)'); + gradient.addColorStop(0.3, 'rgba(255,255,255,0.7)'); + gradient.addColorStop(0.4, 'rgba(255,255,255,0.5)'); + gradient.addColorStop(1, 'rgba(255,255,255,0.1)'); + context.fillStyle = gradient; + context.fill(); + + context.lineWidth = 0.4; + context.stroke(); + + context.restore(); + }, + + erase: function() { + this.context.clearRect(this.LEFT, this.TOP, this.context.canvas.width, this.context.canvas.height); + } +}; diff --git a/canvas/ch09/pinball/requestNextAnimationFrame.js b/canvas/ch09/pinball/requestNextAnimationFrame.js new file mode 100644 index 0000000..e07bfb3 --- /dev/null +++ b/canvas/ch09/pinball/requestNextAnimationFrame.js @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +window.requestNextAnimationFrame = + (function () { + var originalWebkitRequestAnimationFrame = undefined, + wrapper = undefined, + callback = undefined, + geckoVersion = 0, + userAgent = navigator.userAgent, + index = 0, + self = this; + + // Workaround for Chrome 10 bug where Chrome + // does not pass the time to the animation function + + if (window.webkitRequestAnimationFrame) { + // Define the wrapper + + wrapper = function (time) { + if (time === undefined) { + time = +new Date(); + } + self.callback(time); + }; + + // Make the switch + + originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame; + + window.webkitRequestAnimationFrame = function (callback, element) { + self.callback = callback; + + // Browser calls the wrapper and wrapper calls the callback + + originalWebkitRequestAnimationFrame(wrapper, element); + } + } + + // Workaround for Gecko 2.0, which has a bug in + // mozRequestAnimationFrame() that restricts animations + // to 30-40 fps. + + if (window.mozRequestAnimationFrame) { + // Check the Gecko version. Gecko is used by browsers + // other than Firefox. Gecko 2.0 corresponds to + // Firefox 4.0. + + index = userAgent.indexOf('rv:'); + + if (userAgent.indexOf('Gecko') != -1) { + geckoVersion = userAgent.substr(index + 3, 3); + + if (geckoVersion === '2.0') { + // Forces the return statement to fall through + // to the setTimeout() function. + + window.mozRequestAnimationFrame = undefined; + } + } + } + + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + + function (callback, element) { + var start, + finish; + + window.setTimeout( function () { + start = +new Date(); + callback(start); + finish = +new Date(); + + self.timeout = 1000 / 60 - (finish - start); + + }, self.timeout); + }; + } + ) +(); diff --git a/canvas/ch09/pinball/shapes.js b/canvas/ch09/pinball/shapes.js new file mode 100644 index 0000000..ad5f110 --- /dev/null +++ b/canvas/ch09/pinball/shapes.js @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Functions..................................................... + +// .............................................................. +// Check to see if a polygon collides with another polygon +// .............................................................. + +function polygonCollidesWithPolygon (p1, p2, displacement) { // displacement for p1 + var mtv1 = p1.minimumTranslationVector(p1.getAxes(), p2, displacement), + mtv2 = p1.minimumTranslationVector(p2.getAxes(), p2, displacement); + + if (mtv1.overlap === 0 || mtv2.overlap === 0) + return { axis: undefined, overlap: 0 }; + else + return mtv1.overlap < mtv2.overlap ? mtv1 : mtv2; +}; + +// .............................................................. +// Check to see if a circle collides with another circle +// .............................................................. + +function circleCollidesWithCircle (c1, c2) { + var distance = Math.sqrt( Math.pow(c2.x - c1.x, 2) + + Math.pow(c2.y - c1.y, 2)), + overlap = Math.abs(c1.radius + c2.radius) - distance; + + return overlap < 0 ? + new MinimumTranslationVector(undefined, 0) : + new MinimumTranslationVector(undefined, overlap); +}; + +// .............................................................. +// Get the polygon's point that's closest to the circle +// .............................................................. + +function getPolygonPointClosestToCircle(polygon, circle) { + var min = BIG_NUMBER, + length, + testPoint, + closestPoint; + + for (var i=0; i < polygon.points.length; ++i) { + testPoint = polygon.points[i]; + length = Math.sqrt(Math.pow(testPoint.x - circle.x, 2), + Math.pow(testPoint.y - circle.y, 2)); + if (length < min) { + min = length; + closestPoint = testPoint; + } + } + + return closestPoint; +}; + +// .............................................................. +// Get the circle's axis (circle's don't have an axis, so this +// method manufactures one) +// .............................................................. + +function getCircleAxis(circle, polygon, closestPoint) { + var v1 = new Vector(new Point(circle.x, circle.y)), + v2 = new Vector(new Point(closestPoint.x, closestPoint.y)), + surfaceVector = v1.subtract(v2); + + return surfaceVector.normalize(); +}; + +// .............................................................. +// Tests to see if a polygon collides with a circle +// .............................................................. + +function polygonCollidesWithCircle (polygon, circle, displacement) { + var axes = polygon.getAxes(), + closestPoint = getPolygonPointClosestToCircle(polygon, circle); + + axes.push(getCircleAxis(circle, polygon, closestPoint)); + + return polygon.minimumTranslationVector(axes, circle, displacement); +}; + +// .............................................................. +// Given two shapes, and a set of axes, returns the minimum +// translation vector. +// .............................................................. + + +function getMTV(shape1, shape2, displacement, axes) { + var minimumOverlap = BIG_NUMBER, + overlap, + axisWithSmallestOverlap, + mtv; + + for (var i=0; i < axes.length; ++i) { + axis = axes[i]; + projection1 = shape1.project(axis); + projection2 = shape2.project(axis); + overlap = projection1.getOverlap(projection2); + + if (overlap === 0) { + return new MinimumTranslationVector(undefined, 0); + } + else { + if (overlap < minimumOverlap) { + minimumOverlap = overlap; + axisWithSmallestOverlap = axis; + } + } + } + mtv = new MinimumTranslationVector(axisWithSmallestOverlap, + minimumOverlap); + return mtv; +}; + + +// Constants..................................................... + +var BIG_NUMBER = 1000000; + + +// Points........................................................ + +var Point = function (x, y) { + this.x = x; + this.y = y; +}; + +Point.prototype = { + rotate: function (rotationPoint, angle) { + var tx, ty, rx, ry; + + tx = this.x - rotationPoint.x; // tx = translated X + ty = this.y - rotationPoint.y; // ty = translated Y + + rx = tx * Math.cos(-angle) - // rx = rotated X + ty * Math.sin(-angle); + + ry = tx * Math.sin(-angle) + // ry = rotated Y + ty * Math.cos(-angle); + + return new Point(rx + rotationPoint.x, ry + rotationPoint.y); + } +}; + +// Lines......................................................... + +var Line = function(p1, p2) { + this.p1 = p1; // point 1 + this.p2 = p2; // point 2 +} + +Line.prototype.intersectionPoint = function (line) { + var m1, m2, b1, b2, ip = new Point(); + + if (this.p1.x === this.p2.x) { + m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x); + b2 = line.p1.y - m2 * line.p1.x; + ip.x = this.p1.x; + ip.y = m2 * ip.x + b2; + } + else if(line.p1.x === line.p2.x) { + m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); + b1 = this.p1.y - m1 * this.p1.x; + ip.x = line.p1.x; + ip.y = m1 * ip.x + b1; + } + else { + m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); + m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x); + b1 = this.p1.y - m1 * this.p1.x; + b2 = line.p1.y - m2 * line.p1.x; + ip.x = (b2 - b1) / (m1 - m2); + ip.y = m1 * ip.x + b1; + } + return ip; +}; + +// Bounding boxes................................................ + +var BoundingBox = function(left, top, width, height) { + this.left = left; + this.top = top; + this.width = width; + this.height = height; +}; + + +// Vectors....................................................... + +var Vector = function(point) { + if (point === undefined) { + this.x = 0; + this.y = 0; + } + else { + this.x = point.x; + this.y = point.y; + } +}; + +Vector.prototype = { + getMagnitude: function () { + return Math.sqrt(Math.pow(this.x, 2) + + Math.pow(this.y, 2)); + }, + + setMagnitude: function (m) { + var uv = this.normalize(); + this.x = uv.x * m; + this.y = uv.y * m; + }, + + dotProduct: function (vector) { + return this.x * vector.x + + this.y * vector.y; + }, + + add: function (vector) { + var v = new Vector(); + v.x = this.x + vector.x; + v.y = this.y + vector.y; + return v; + }, + + subtract: function (vector) { + var v = new Vector(); + v.x = this.x - vector.x; + v.y = this.y - vector.y; + return v; + }, + + normalize: function () { + var v = new Vector(), + m = this.getMagnitude(); + v.x = this.x / m; + v.y = this.y / m; + return v; + }, + + perpendicular: function () { + var v = new Vector(); + v.x = this.y; + v.y = 0-this.x; + return v; + }, + + reflect: function (axis) { + var dotProductRatio, vdotl, ldotl, v = new Vector(), + vdotl = this.dotProduct(axis), + ldotl = axis.dotProduct(axis), + dotProductRatio = vdotl / ldotl; + + v.x = 2 * dotProductRatio * axis.x - this.x; + v.y = 2 * dotProductRatio * axis.y - this.y; + + return v; + } +}; + + +// Shapes........................................................ + +var Shape = function () { + this.fillStyle = 'rgba(255, 255, 0, 0.8)'; + this.strokeStyle = 'white'; +}; + +Shape.prototype = { + move: function (dx, dy) { + throw 'move(dx, dy) not implemented'; + }, + + createPath: function (context) { + throw 'createPath(context) not implemented'; + }, + + boundingBox: function () { + throw 'boundingBox() not implemented'; + }, + + fill: function (context) { + context.save(); + context.fillStyle = this.fillStyle; + this.createPath(context); + context.fill(); + context.restore(); + }, + + stroke: function (context) { + context.save(); + context.strokeStyle = this.strokeStyle; + this.createPath(context); + context.stroke(); + context.restore(); + }, + + collidesWith: function (shape, displacement) { + throw 'collidesWith(shape, displacement) not implemented'; + }, + + isPointInPath: function (context, x, y) { + this.createPath(context); + return context.isPointInPath(x, y); + }, + + project: function (axis) { + throw 'project(axis) not implemented'; + }, + + minimumTranslationVector: function (axes, shape, displacement) { + return getMTV(this, shape, displacement, axes); + } +}; + + +// Circles....................................................... + +var Circle = function (x, y, radius) { + this.x = x; + this.y = y; + this.radius = radius; + this.strokeStyle = 'blue'; + this.fillStyle = 'yellow'; +} + +Circle.prototype = new Shape(); + +Circle.prototype.centroid = function () { + return new Point(this.x,this.y); +}; + +Circle.prototype.move = function (dx, dy) { + this.x += dx; + this.y += dy; +}; + +Circle.prototype.boundingBox = function (dx, dy) { + return new BoundingBox(this.x - this.radius, + this.y - this.radius, + 2*this.radius, + 2*this.radius); +}; + +Circle.prototype.createPath = function (context) { + context.beginPath(); + context.arc(this.x, this.y, this.radius, 0, Math.PI*2, false); +}; + +Circle.prototype.project = function (axis) { + var scalars = [], + point = new Point(this.x, this.y); + dotProduct = new Vector(point).dotProduct(axis); + + scalars.push(dotProduct); + scalars.push(dotProduct + this.radius); + scalars.push(dotProduct - this.radius); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Circle.prototype.collidesWith = function (shape, displacement) { + if (shape.radius === undefined) { + return polygonCollidesWithCircle(shape, this, displacement); + } + else { + return circleCollidesWithCircle(this, shape, displacement); + } +}; + + +// Polygons...................................................... + +var Polygon = function () { + this.points = []; + this.strokeStyle = 'blue'; + this.fillStyle = 'white'; +}; + +Polygon.prototype = new Shape(); + +Polygon.prototype.getAxes = function () { + var v1, v2, surfaceVector, axes = [], pushAxis = true; + + for (var i=0; i < this.points.length-1; i++) { + v1 = new Vector(this.points[i]); + v2 = new Vector(this.points[i+1]); + + surfaceVector = v2.subtract(v1); + axes.push(surfaceVector.perpendicular().normalize()); + } + + return axes; +}; + +Polygon.prototype.project = function (axis) { + var scalars = []; + + this.points.forEach( function (point) { + scalars.push(new Vector(point).dotProduct(axis)); + }); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Polygon.prototype.addPoint = function (x, y) { + this.points.push(new Point(x,y)); +}; + +Polygon.prototype.createPath = function (context) { + if (this.points.length === 0) + return; + + context.beginPath(); + context.moveTo(this.points[0].x, + this.points[0].y); + + for (var i=0; i < this.points.length; ++i) { + context.lineTo(this.points[i].x, + this.points[i].y); + } +}; + +Polygon.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + x += dx; + y += dy; + } +}; + +Polygon.prototype.collidesWith = function (shape, displacement) { + if (shape.radius !== undefined) { + return polygonCollidesWithCircle(this, shape, displacement); + } + else { + return polygonCollidesWithPolygon(this, shape, displacement); + } +}; + +Polygon.prototype.move = function (dx, dy) { + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +Polygon.prototype.boundingBox = function (dx, dy) { + var minx = BIG_NUMBER, + miny = BIG_NUMBER, + maxx = -BIG_NUMBER, + maxy = -BIG_NUMBER, + point; + + for (var i=0; i < this.points.length; ++i) { + point = this.points[i]; + minx = Math.min(minx,point.x); + miny = Math.min(miny,point.y); + maxx = Math.max(maxx,point.x); + maxy = Math.max(maxy,point.y); + } + + return new BoundingBox(minx, miny, + parseFloat(maxx - minx), + parseFloat(maxy - miny)); +}; + +Polygon.prototype.centroid = function () { + var pointSum = new Point(0,0); + + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + pointSum.x += point.x; + pointSum.y += point.y; + } + return new Point(pointSum.x/this.points.length, pointSum.y/this.points.length); +} + +// Projections................................................... + +var Projection = function (min, max) { + this.min = min; + this.max = max; +}; + +Projection.prototype = { + overlaps: function (projection) { + return this.max > projection.min && projection.max > this.min; + }, + + getOverlap: function (projection) { + var overlap; + + if (!this.overlaps(projection)) + return 0; + + if (this.max > projection.max) { + overlap = projection.max - this.min; + } + else { + overlap = this.max - projection.min; + } + return overlap; + } +}; + + +// MinimumTranslationVector......................................... + +var MinimumTranslationVector = function (axis, overlap) { + this.axis = axis; + this.overlap = overlap; +}; + +var ImageShape = function(imageSource, x, y, w, h) { + var self = this; + + this.image = new Image(); + this.imageLoaded = false; + this.points = [ new Point(x,y) ]; + this.x = x; + this.y = y; + + this.image.src = imageSource; + + this.image.addEventListener('load', function (e) { + self.setPolygonPoints(); + self.imageLoaded = true; + }, false); +} + +ImageShape.prototype = new Polygon(); + +ImageShape.prototype.fill = function (context) { +}; + +ImageShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x + this.image.width, this.y)); + this.points.push(new Point(this.x + this.image.width, this.y + this.image.height)); + this.points.push(new Point(this.x, this.y + this.image.height)); + this.points.push(new Point(this.x, this.y)); + this.points.push(new Point(this.x + this.image.width, this.y)); +}; + +ImageShape.prototype.drawImage = function (context) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); +}; + +ImageShape.prototype.stroke = function (context) { + var self = this; + + if (this.imageLoaded) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); + } + else { + this.image.addEventListener('load', function (e) { + self.drawImage(context); + }, false); + } +}; + + +var SpriteShape = function (sprite, x, y) { + this.sprite = sprite; + this.x = x; + this.y = y; + sprite.left = x; + sprite.top = y; + this.setPolygonPoints(); +}; + +SpriteShape.prototype = new Polygon(); + +SpriteShape.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } + this.sprite.left = this.points[0].x; + this.sprite.top = this.points[0].y; +}; + +SpriteShape.prototype.fill = function (context) { +}; + +SpriteShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x, this.y)); + this.points.push(new Point(this.x, this.y + this.sprite.height)); + this.points.push(new Point(this.x + this.sprite.width, this.y + this.sprite.height)); + this.points.push(new Point(this.x + this.sprite.width, this.y)); + this.points.push(new Point(this.x, this.y)); +}; +/* +SpriteShape.prototype.stroke = function (context) { + this.sprite.paint(context); +}; +*/ + diff --git a/canvas/ch09/pinball/sounds/ballRolling.ogg b/canvas/ch09/pinball/sounds/ballRolling.ogg new file mode 100644 index 0000000..cda9d8b Binary files /dev/null and b/canvas/ch09/pinball/sounds/ballRolling.ogg differ diff --git a/canvas/ch09/pinball/sounds/bumper.ogg b/canvas/ch09/pinball/sounds/bumper.ogg new file mode 100644 index 0000000..8d20bb0 Binary files /dev/null and b/canvas/ch09/pinball/sounds/bumper.ogg differ diff --git a/canvas/ch09/pinball/sounds/flipper.ogg b/canvas/ch09/pinball/sounds/flipper.ogg new file mode 100644 index 0000000..be61ca1 Binary files /dev/null and b/canvas/ch09/pinball/sounds/flipper.ogg differ diff --git a/canvas/ch09/pinball/sounds/jingle.mp3 b/canvas/ch09/pinball/sounds/jingle.mp3 new file mode 100644 index 0000000..d0eee95 Binary files /dev/null and b/canvas/ch09/pinball/sounds/jingle.mp3 differ diff --git a/canvas/ch09/pinball/sounds/jingle.ogg b/canvas/ch09/pinball/sounds/jingle.ogg new file mode 100644 index 0000000..44c8371 Binary files /dev/null and b/canvas/ch09/pinball/sounds/jingle.ogg differ diff --git a/canvas/ch09/pinball/sounds/pinball.ogg b/canvas/ch09/pinball/sounds/pinball.ogg new file mode 100644 index 0000000..44c8371 Binary files /dev/null and b/canvas/ch09/pinball/sounds/pinball.ogg differ diff --git a/canvas/ch09/pinball/sprites.js b/canvas/ch09/pinball/sprites.js new file mode 100644 index 0000000..c8ba3cd --- /dev/null +++ b/canvas/ch09/pinball/sprites.js @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Painters................................................................... + +// Painters paint sprites with a paint(sprite, context) method. ImagePainters +// paint an image for their sprite. + +var ImagePainter = function (imageUrl) { + this.image = new Image; + this.image.src = imageUrl; +}; + +ImagePainter.prototype = { + image: undefined, + + paint: function (sprite, context) { + if (this.image !== undefined) { + if ( ! this.image.complete) { + this.image.onload = function (e) { + sprite.width = this.width; + sprite.height = this.height; + + context.drawImage(this, // this is image + sprite.left, sprite.top, + sprite.width, sprite.height); + }; + } + else { + context.drawImage(this.image, sprite.left, sprite.top, + sprite.width, sprite.height); + } + } + } +}; + +SpriteSheetPainter = function (cells) { + this.cells = cells; +}; + +SpriteSheetPainter.prototype = { + cells: [], + cellIndex: 0, + + advance: function () { + if (this.cellIndex == this.cells.length-1) { + this.cellIndex = 0; + } + else { + this.cellIndex++; + } + }, + + paint: function (sprite, context) { + var cell = this.cells[this.cellIndex]; + context.drawImage(spritesheet, cell.x, cell.y, cell.w, cell.h, + sprite.left, sprite.top, cell.w, cell.h); + } +}; + +// Sprite Animators........................................................... + +// Sprite animators have an array of painters that they succesively apply +// to a sprite over a period of time. Animators can be started with +// start(sprite, durationInMillis, restoreSprite) + +var SpriteAnimator = function (painters, elapsedCallback) { + this.painters = painters; + if (elapsedCallback) { + this.elapsedCallback = elapsedCallback; + } +}; + +SpriteAnimator.prototype = { + painters: [], + duration: 1000, + startTime: 0, + index: 0, + elapsedCallback: undefined, + + end: function (sprite, originalPainter) { + sprite.animating = false; + + if (this.elapsedCallback) { + this.elapsedCallback(sprite); + } + else { + sprite.painter = originalPainter; + } + }, + + start: function (sprite, duration) { + var endTime = +new Date() + duration, + period = duration / (this.painters.length), + interval = undefined, + animator = this, // for setInterval() function + originalPainter = sprite.painter; + + this.index = 0; + sprite.animating = true; + sprite.painter = this.painters[this.index]; + + interval = setInterval(function() { + if (+new Date() < endTime) { + sprite.painter = animator.painters[++animator.index]; + } + else { + animator.end(sprite, originalPainter); + clearInterval(interval); + } + }, period); + }, +}; + +// Sprites.................................................................... + +// Sprites have a name, a painter, and an array of behaviors. Sprites can +// be updated, and painted. +// +// A sprite's painter paints the sprite: paint(sprite, context) +// A sprite's behavior executes: execute(sprite, context, time) + +var Sprite = function (name, painter, behaviors) { + if (name !== undefined) this.name = name; + if (painter !== undefined) this.painter = painter; + if (behaviors !== undefined) this.behaviors = behaviors; + + return this; +}; + +Sprite.prototype = { + left: 0, + top: 0, + width: 10, + height: 10, + velocityX: 0, + velocityY: 0, + visible: true, + animating: false, + painter: undefined, // object with paint(sprite, context) + behaviors: [], // objects with execute(sprite, context, time) + + paint: function (context) { + if (this.painter !== undefined && this.visible) { + this.painter.paint(this, context); + } + }, + + update: function (context, time) { + for (var i = this.behaviors.length; i > 0; --i) { + this.behaviors[i-1].execute(this, context, time); + } + } +}; diff --git a/canvas/ch09/pinball/stopwatch.js b/canvas/ch09/pinball/stopwatch.js new file mode 100644 index 0000000..63a57eb --- /dev/null +++ b/canvas/ch09/pinball/stopwatch.js @@ -0,0 +1,71 @@ +// Stopwatch.................................................................. +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// +// Like the real thing, you can start and stop a stopwatch, and you can +// find out the elapsed time the stopwatch has been running. After you stop +// a stopwatch, it's getElapsedTime() method returns the elapsed time +// between the start and stop. + +Stopwatch = function () { +}; + +// You can get the elapsed time while the timer is running, or after it's +// stopped. + +Stopwatch.prototype = { + startTime: 0, + running: false, + elapsedTime: 0, + + start: function () { + this.startTime = +new Date(); + this.elapsedTime = 0; + this.running = true; + }, + + stop: function () { + this.elapsedTime = +new Date() - this.startTime; + this.running = false; + }, + + getElapsedTime: function () { + if (this.running) return +new Date() - this.startTime; + else return this.elapsedTime; + }, + + reset: function() { + this.elapsedTime = 0; + this.startTime = 0; + this.running = false; + } +}; diff --git a/canvas/ch09/ungame/images/image1.png b/canvas/ch09/ungame/images/image1.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image1.png differ diff --git a/canvas/ch09/ungame/images/image10.png b/canvas/ch09/ungame/images/image10.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image10.png differ diff --git a/canvas/ch09/ungame/images/image11.png b/canvas/ch09/ungame/images/image11.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image11.png differ diff --git a/canvas/ch09/ungame/images/image12.png b/canvas/ch09/ungame/images/image12.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image12.png differ diff --git a/canvas/ch09/ungame/images/image2.png b/canvas/ch09/ungame/images/image2.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image2.png differ diff --git a/canvas/ch09/ungame/images/image3.png b/canvas/ch09/ungame/images/image3.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image3.png differ diff --git a/canvas/ch09/ungame/images/image4.png b/canvas/ch09/ungame/images/image4.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image4.png differ diff --git a/canvas/ch09/ungame/images/image5.png b/canvas/ch09/ungame/images/image5.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image5.png differ diff --git a/canvas/ch09/ungame/images/image6.png b/canvas/ch09/ungame/images/image6.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image6.png differ diff --git a/canvas/ch09/ungame/images/image7.png b/canvas/ch09/ungame/images/image7.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image7.png differ diff --git a/canvas/ch09/ungame/images/image8.png b/canvas/ch09/ungame/images/image8.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image8.png differ diff --git a/canvas/ch09/ungame/images/image9.png b/canvas/ch09/ungame/images/image9.png new file mode 100644 index 0000000..f44b410 Binary files /dev/null and b/canvas/ch09/ungame/images/image9.png differ diff --git a/canvas/ch09/ungame/progressbar.js b/canvas/ch09/ungame/progressbar.js new file mode 100644 index 0000000..f4d00d3 --- /dev/null +++ b/canvas/ch09/ungame/progressbar.js @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {} + +COREHTML5.Progressbar = function(w, h, strokeStyle, red, green, blue) { + this.domElement = document.createElement('div'); + this.context = document.createElement('canvas').getContext('2d'); + this.domElement.appendChild(this.context.canvas); + + this.context.canvas.width = w + h; // On each end, corner radius = h/2 + this.context.canvas.height = h; + + this.setProgressbarProperties(w, h); + + this.background.globalAlpha = 0.3; + this.drawToBuffer(this.background, strokeStyle, red, green, blue); + this.drawToBuffer(this.foreground, strokeStyle, red, green, blue); + + this.percentComplete = 0; + return this; +} + +COREHTML5.Progressbar.prototype = { + LEFT: 0, + TOP: 0, + + setProgressbarProperties: function(w, h) { + this.w = w; + this.h = h; + this.cornerRadius = this.h/2, + this.right = this.LEFT + this.cornerRadius + this.w + this.cornerRadius, + this.bottom = this.TOP + this.h; + + this.background = document.createElement('canvas').getContext('2d'), + this.foreground = document.createElement('canvas').getContext('2d'), + + this.background.canvas.width = w + h; // On each end, corner radius = h/2 + this.background.canvas.height = h; + + this.foreground.canvas.width = w + h; // On each end, corner radius = h/2 + this.foreground.canvas.height = h; + }, + + draw: function (percentComplete) { + this.erase(); + this.context.drawImage(this.background.canvas, 0, 0); + + if (percentComplete > 0) { + this.context.drawImage(this.foreground.canvas, 0, 0, + this.foreground.canvas.width*(percentComplete/100), + this.foreground.canvas.height, + 0, 0, + this.foreground.canvas.width*(percentComplete/100), + this.foreground.canvas.height); + } + }, + + drawToBuffer: function (context, strokeStyle, red, green, blue) { + context.save(); + + context.fillStyle = 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + context.strokeStyle = strokeStyle; + + context.beginPath(); + + context.moveTo(this.LEFT + this.cornerRadius, this.TOP); + context.lineTo(this.right - this.cornerRadius, this.TOP); + + context.arc(this.right - this.cornerRadius, + this.TOP + this.cornerRadius, this.cornerRadius, -Math.PI/2, Math.PI/2); + + context.lineTo(this.LEFT + this.cornerRadius, + this.TOP + this.cornerRadius*2); + + context.arc(this.LEFT + this.cornerRadius, + this.TOP + this.cornerRadius, this.cornerRadius, Math.PI/2, -Math.PI/2); + + context.fill(); + + context.shadowColor = undefined; + + var gradient = context.createLinearGradient(this.LEFT, this.TOP, this.LEFT, this.bottom); + gradient.addColorStop(0, 'rgba(255,255,255,0.4)'); + gradient.addColorStop(0.3, 'rgba(255,255,255,0.7)'); + gradient.addColorStop(0.4, 'rgba(255,255,255,0.5)'); + gradient.addColorStop(1, 'rgba(255,255,255,0.1)'); + context.fillStyle = gradient; + context.fill(); + + context.lineWidth = 0.4; + context.stroke(); + + context.restore(); + }, + + erase: function() { + this.context.clearRect(this.LEFT, this.TOP, this.context.canvas.width, this.context.canvas.height); + } +}; diff --git a/canvas/ch09/ungame/sounds/pop.mp3 b/canvas/ch09/ungame/sounds/pop.mp3 new file mode 100644 index 0000000..01bf0f6 Binary files /dev/null and b/canvas/ch09/ungame/sounds/pop.mp3 differ diff --git a/canvas/ch09/ungame/sounds/pop.ogg b/canvas/ch09/ungame/sounds/pop.ogg new file mode 100644 index 0000000..08a218e Binary files /dev/null and b/canvas/ch09/ungame/sounds/pop.ogg differ diff --git a/canvas/ch09/ungame/sounds/whoosh.mp3 b/canvas/ch09/ungame/sounds/whoosh.mp3 new file mode 100644 index 0000000..7fb2ac1 Binary files /dev/null and b/canvas/ch09/ungame/sounds/whoosh.mp3 differ diff --git a/canvas/ch09/ungame/sounds/whoosh.ogg b/canvas/ch09/ungame/sounds/whoosh.ogg new file mode 100644 index 0000000..3afd157 Binary files /dev/null and b/canvas/ch09/ungame/sounds/whoosh.ogg differ diff --git a/canvas/ch09/ungame/ungame.css b/canvas/ch09/ungame/ungame.css new file mode 100644 index 0000000..5e381e6 --- /dev/null +++ b/canvas/ch09/ungame/ungame.css @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +#loadingToast { + padding: 20px; + position: absolute; + left: 50px; + top: 180px; + width: 450px; + height: 400px; + display: block; +} + +#loadingToast .title { + padding-left: 177px; + font: 16px Arial; +} + +#loadingToast p { + margin-left: 10px; + margin-right: 10px; + color: black; +} + +#highScoreParagraph { + position: absolute; + left: 150px; + top: -50px; + color: red; + font: 8em fantasy; + margin-left: 70px; + margin-bottom: 50px; +} + +#highScoreToast { + position: absolute; + left: 0px; + top: 200px; + color: cornflowerblue; + margin-left: 150px; + margin-top: 20px; + font: 18px fantasy; +} + +#livesCanvas { + background: rgba(255,255,255,0.3); + position: absolute; + left: 475px; + top: 25px; + border: thin solid rbga(100,140,230,0.8); + -webkit-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; +} + +#gameCanvas { + margin: 20px; + background: rgba(120,168,249,0.7); + position: absolute; + left: 0px; + top: 0px; + -webkit-box-shadow: rgba(100,100,100,0.5) 4px 4px 8px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + border: thin solid cornflowerblue; +} + +.floatingControls { + background: rgba(0, 0, 0, 0.1); + border: thin solid skyblue; + -webkit-box-shadow: rgba(0,0,0,0.3) 2px 2px 4px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + position: absolute; +} + +.floatingControls a { + font-size: 1.5em; + text-decoration: none; + color: rgba(255,255,0,0.6); +} + +.floatingControls a:hover { + color: rgba(255,255,0,1.0); +} + +#instructionsLink { + color: cornflowerblue; + text-decoration: none; +} + +#instructionsLink:hover { + color: rgba(255,255,0,1.0); +} + +#instructions p.title { + font-size: 1.5em; + color: blue; +} + +#instructions { + margin-left: 60px; + margin-top: 50px; + padding-left: 20px; + padding-right: 20px; + width: 700px; + height: 370px; + -webkit-box-shadow: rgba(0,0,0,0.3) 4px 4px 8px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + color: rgba(0, 0, 255, 0.8); + background: rgba(255, 255, 255, 0.8); +} + +#instructionsOkayButtonDiv { + left: 0px; + width: 100%; + text-align: center; +} + +#instructionsOkayButton { + margin-top: 30px; +} + +.toast { + background: rgba(255, 255, 255, 0.7); + border: thin solid skyblue; + -webkit-box-shadow: rgba(0,0,0,0.3) 2px 2px 4px; + -moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + -o-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px; + position: absolute; + display: none; +} + +#pausedToast { + padding: 5px 40px 20px 40px; + margin-left: 175px; + margin-top: 100px; + color: blue; +} + +#pausedToast p { + color: blue; +} + +#pausedToast p.title { + font-size: 1.50em; + color: blue; +} + +#scoreToast { + background: rgba(255,255,255,0.5); + left: 25px; + top: 25px; + padding: 5px; + font-size: 1.25em; + color: rgba(0,0,250,1.0); +} + +div .title { + color: blue; +} + +div p { + color: blue; +} + +div a { + text-decoration: none; + color: cornflowerblue; +} + +p.title { + font-size: 1.5em; +} + +#gameOverToast { + padding-left: 30px; + padding-right: 30px; + padding-bottom: 10px; + margin-left: 203px; + margin-top: 100px; + text-align: center; + display: none; +} + +#highScoreList { + color: rgba(0,0,255,0.6); +} + +#previousHighScoresTitle { + margin-top: 50px; +} + +#loseLifeToast { + padding: 10px; + position: absolute; + left: 250px; + top: 300px; +} + +#loadButtonSpan { + position: absolute; + left: 200px; + padding-top: 20px; +} + +.blurb { + padding: 10px; + display: block; + font: 12px Arial; +} + +#progressDiv { + padding-left: 60px; + padding-top: 45px; +} + +#loadingMessage { + display: none; + font: 13px Helvetica; + padding-left: 20px; +} diff --git a/canvas/ch09/ungame/ungame.html b/canvas/ch09/ungame/ungame.html new file mode 100644 index 0000000..eb6506e --- /dev/null +++ b/canvas/ch09/ungame/ungame.html @@ -0,0 +1,153 @@ + + + + + + Ungame + + + + + + + + Canvas not supported + + + + +
+ The Ungame + + +

This game is an ungame, sort of like the undead: The undead are not + really dead, and this is not really a game; however, it implements + essential functionality pertient to most games.

+ +

The ungame comes with:

+ + + +

The ungame is implemented with a + simple game engine (~200 lines of JavaScript).

+
+ + + + Loading... + + +
+
+ + + +
+ + + + + Canvas not supported + + + + +
+

Paused

+

Click anywhere to start

+
+ + + +
+

Game Over


+

clear high scores

+ +
+ + + +

+ + + + + +
+ +
+ + + + + + + + + + + + + + diff --git a/canvas/ch09/ungame/ungame.js b/canvas/ch09/ungame/ungame.js new file mode 100644 index 0000000..826d1cf --- /dev/null +++ b/canvas/ch09/ungame/ungame.js @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var game = new Game('ungame', 'gameCanvas'), + +// Loading.................................................... + +loading = false, // not yet, see the end of this file +loadingToast = document.getElementById('loadingToast'), +loadingMessage = document.getElementById('loadingMessage'), +loadingToastTitle = document.getElementById('loadingToastTitle'), +loadingToastBlurb = document.getElementById('loadingToastBlurb'), +loadButton = document.getElementById('loadButton'), +progressDiv = document.getElementById('progressDiv'), +progressbar = new COREHTML5.Progressbar(300, 25, 'rgba(0,0,0,0.5)', 100, 130, 250), + +// Score...................................................... + +scoreToast = document.getElementById('scoreToast'), +scoreReadout = document.getElementById('score'), +score = 0, +lastScore = 0, +lastScoreUpdate = undefined, + +// High Score................................................. + +HIGH_SCORES_DISPLAYED = 10, + +highScoreToast = document.getElementById('highScoreToast'), +highScoreParagraph = document.getElementById('highScoreParagraph'), +highScoreList = document.getElementById('highScoreList'), +previousHighScoresTitle = document.getElementById('previousHighScoresTitle'), +nameInput = document.getElementById('nameInput'), +addMyScoreButton = document.getElementById('addMyScoreButton'), +newGameButton = document.getElementById('newGameButton'), +newGameFromHighScoresButton = + document.getElementById('newGameFromHighScoresButton'), +clearHighScoresCheckbox = document.getElementById('clearHighScoresCheckbox'), + +// Lives...................................................... + +livesCanvas = document.getElementById('livesCanvas'), +livesContext = livesCanvas.getContext('2d'), +livesLeft = 3, +life = 100, + +// Paused..................................................... + +pausedToast = document.getElementById('pausedToast'), + +// Game Over.................................................. + +gameOverToast = document.getElementById('gameOverToast'), +gameOver = false, + +// Sun Constants.............................................. + +SUN_TOP = 110, +SUN_LEFT = 450, +SUN_RADIUS = 80, + +// Key Listeners.............................................. + +lastKeyListenerTime = 0, // For throttling arrow keys, see below + +// Lose life.................................................. + +loseLifeToast = document.getElementById('loseLifeToast'), +loseLifeButton = document.getElementById('loseLifeButton'), + +// Scrolling the background................................... + +translateDelta = 0.025, +translateOffset = 0, + +scrollBackground = function () { + translateOffset = + (translateOffset + translateDelta) % game.context.canvas.width; + game.context.translate(-translateOffset,0); +}, + +// Paint Methods.............................................. + +paintSun = function (context) { + context.save(); + + context.strokeStyle = 'orange'; + context.fillStyle = 'yellow'; + context.strokeStyle = 'orange'; + context.lineWidth = 1; + + context.beginPath(); + context.arc(SUN_LEFT, SUN_TOP, SUN_RADIUS, 0, Math.PI*2, true); + context.fill(); + context.stroke(); + + context.stroke(); + context.restore(); +}, + +paintFarCloud = function (context, x, y) { + context.save(); + scrollBackground(); + context.lineWidth=0.5; + context.strokeStyle='rgba(100, 140, 230, 0, 0.8)'; + context.fillStyle='rgba(255,255,255,0.4)'; + context.beginPath(); + + context.moveTo(x+102, y+91); + context.quadraticCurveTo(x+180, y+110, x+250, y+90); + context.quadraticCurveTo(x+312, y+87, x+279, y+60); + context.quadraticCurveTo(x+321, y+20, x+265, y+20); + context.quadraticCurveTo(x+219, y+4, x+171, y+23); + context.quadraticCurveTo(x+137, y+5, x+104, y+18); + context.quadraticCurveTo(x+57, y+23, x+79, y+48); + context.quadraticCurveTo(x+57, y+74, x+104, y+92); + context.closePath(); + context.stroke(); + context.fill(); + context.restore(); +}, + +paintNearCloud = function (context, x, y) { + context.save(); + scrollBackground(); + scrollBackground(); + context.lineWidth=0.5; + context.strokeStyle='rgba(100, 140, 230, 0, 0.8)'; + context.fillStyle='rgba(255,255,255,0.4)'; + context.beginPath(); + + context.fillStyle='rgba(255,255,255,0.7)'; + + context.moveTo(x+364, y+37); + context.quadraticCurveTo(x+426, y+28, x+418, y+72); + context.quadraticCurveTo(x+450, y+123, x+388, y+114); + context.quadraticCurveTo(x+357, y+144, x+303,y+ 115); + context.quadraticCurveTo(x+251, y+118, x+278, y+83); + context.quadraticCurveTo(x+254, y+46, x+320, y+46); + context.quadraticCurveTo(x+326, y+12, x+362, y+37); + context.closePath(); + context.stroke(); + context.fill(); + context.restore(); +}, + +// Game over.................................................. + +over = function () { + var highScore; + highScores = game.getHighScores(); + + if (highScores.length == 0 || score > highScores[0].score) { + showHighScores(); + } + else { + gameOverToast.style.display = 'inline'; + } + + gameOver = true; + lastScore = score; + score = 0; +}; + + +// Pause and Auto-pause....................................... + +togglePaused = function () { + game.togglePaused(); + pausedToast.style.display = game.paused ? 'inline' : 'none'; +}; + +pausedToast.onclick = function (e) { + pausedToast.style.display = 'none'; + togglePaused(); +}; + +window.onblur = function windowOnBlur() { + if (!loading && !gameOver && !game.paused) { + togglePaused(); + pausedToast.style.display = game.paused ? 'inline' : 'none'; + } +}; + +window.onfocus = function windowOnFocus() { + if (game.paused) { + togglePaused(); + pausedToast.style.display = game.paused ? 'inline' : 'none'; + } +}; + + +// New game .................................................. + +newGameButton.onclick = function (e) { + gameOverToast.style.display = 'none'; + loseLifeToast.style.display = 'inline'; + startNewGame(); +}; + +function startNewGame() { + highScoreParagraph.style.display = 'none'; + gameOver = false; + livesLeft = 3; + score = 0; + loseLifeButton.focus(); +}; + +// High Scores................................................ + +// Change game display to show high scores when +// player bests the high score. + +showHighScores = function () { + highScoreParagraph.style.display = 'inline'; + highScoreParagraph.innerHTML = score; + highScoreToast.style.display = 'inline'; + updateHighScoreList(); + nameInput.focus(); +}; + +// The game shows the list of high scores in +// an ordered list. This method creates that +// list element, and populates it with the +// current high scores. + +updateHighScoreList = function () { + var el, + highScores = game.getHighScores(), + length = highScores.length, + highScore, + listParent = highScoreList.parentNode; + + listParent.removeChild(highScoreList); + highScoreList = document.createElement('ol'); + highScoreList.id = 'highScoreList'; // So CSS takes effect + listParent.appendChild(highScoreList); + + if (length > 0) { + previousHighScoresTitle.style.display = 'block'; + + length = length > 10 ? 10 : length; + + for (var i=0; i < length; ++i) { + + highScore = highScores[i]; + el = document.createElement('li'); + el.innerHTML = highScore.score + + ' by ' + highScore.name; + highScoreList.appendChild(el); + } + } + else { + previousHighScoresTitle.style.display = 'none'; + } +} + +// The browser invokes this method when the user clicks on the +// Add My Score button. + +addMyScoreButton.onclick = function (e) { + game.setHighScore({ name: nameInput.value, score: lastScore }); + updateHighScoreList(); + addMyScoreButton.disabled = 'true'; + nameInput.value = ''; +}; + + +// The browser invokes this method when the user clicks on the +// new game button. + +newGameFromHighScoresButton.onclick = function (e) { + loseLifeToast.style.display = 'inline'; + highScoreToast.style.display = 'none'; + startNewGame(); +}; + +// The Add My Score button is only enabled when there +// is something in the nameInput field. + +nameInput.onkeyup = function (e) { + if (nameInput.value.length > 0) { + addMyScoreButton.disabled = false; + } + else { + addMyScoreButton.disabled = true; + } +}; + +// Score Display.............................................. + +updateScore = function () { + if ( !loading && game.lastScoreUpdate !== undefined) { + if (game.gameTime - game.lastScoreUpdate > 1000) { + scoreToast.style.display = 'inline'; + score += 10; + scoreToast.innerHTML = score.toFixed(0); + game.lastScoreUpdate = game.gameTime; + } + } + else { + game.lastScoreUpdate = game.gameTime; + } +}; + +// Lives Display.............................................. + +updateLivesDisplay = function () { + var x, y, RADIUS = 10; + + livesContext.clearRect(0,0,livesCanvas.width,livesCanvas.height); + + for (var i=0; i < livesLeft; ++i) { + x = 20 + i*25; + y = 20; + + livesContext.beginPath(); + livesContext.arc(x, y, RADIUS, 0, Math.PI*2, false); + livesContext.fill(); + livesContext.strokeText(parseInt(i+1), x-RADIUS/3, y+RADIUS/3); + livesContext.stroke(); + } +}; + +// Game Paint Methods......................................... + +game.paintOverSprites = function () { + paintNearCloud(game.context, 120, 20); + paintNearCloud(game.context, game.context.canvas.width+120, 20); +} + +game.paintUnderSprites = function () { // Draw things other than sprites + if (!gameOver && livesLeft === 0) { + over(); + } + else { + paintSun(game.context); + paintFarCloud(game.context, 20, 20); + paintFarCloud(game.context, game.context.canvas.width+20, 20); + + if (!gameOver) { + updateScore(); + } + updateLivesDisplay(); + } +}; + +// Key Listeners.............................................. + +game.addKeyListener( + { + key: 'p', + listener: function () { + game.togglePaused(); + } + } +); + +game.addKeyListener( + { + key: 'right arrow', + listener: function () { + var now = +new Date(); + if (now - lastKeyListenerTime > 200) { // throttle + lastKeyListenerTime = now; + } + } + } +); + +game.addKeyListener( + { + key: 'left arrow', + listener: function () { + var now = +new Date(); + if (now - lastKeyListenerTime > 200) { // throttle + lastKeyListenerTime = now; + } + } + } +); + +// Initialization............................................. + +livesContext.strokeStyle = 'slateblue'; +livesContext.fillStyle = 'yellow'; + +// End game button............................................ + +loseLifeButton.onclick = function (e) { + livesLeft--; + game.playSound('whoosh'); + + if (livesLeft === 0) { + loseLifeToast.style.display = 'none'; + } +}; + +clearHighScoresCheckbox.onclick = function (e) { + if (clearHighScoresCheckbox.checked) { + game.clearHighScores(); + } +}; + +// Load game.................................................. + +loading = true; + +loadButton.onclick = function (e) { + var interval, + loadingPercentComplete = 0; + + e.preventDefault(); + + loadButton.style.display = 'none'; + + loadingMessage.style.display = 'block'; + progressDiv.style.display = 'block'; + + progressDiv.appendChild(progressbar.domElement); + + game.queueImage('images/image1.png'); + game.queueImage('images/image2.png'); + game.queueImage('images/image3.png'); + game.queueImage('images/image4.png'); + game.queueImage('images/image5.png'); + game.queueImage('images/image6.png'); + game.queueImage('images/image7.png'); + game.queueImage('images/image8.png'); + game.queueImage('images/image9.png'); + game.queueImage('images/image10.png'); + game.queueImage('images/image11.png'); + game.queueImage('images/image12.png'); + + interval = setInterval( function (e) { + loadingPercentComplete = game.loadImages(); + + if (loadingPercentComplete === 100) { + clearInterval(interval); + + setTimeout( function (e) { + loadingMessage.style.display = 'none'; + progressDiv.style.display = 'none'; + + setTimeout( function (e) { + loadingToastBlurb.style.display = 'none'; + loadingToastTitle.style.display = 'none'; + + setTimeout( function (e) { + loadingToast.style.display = 'none'; + loseLifeToast.style.display = 'block'; + game.playSound('pop'); + + setTimeout( function (e) { + loading = false; + score = 10; + scoreToast.innerText = '10'; // won't get set till later, otherwise + scoreToast.style.display = 'inline'; + game.playSound('pop'); + loseLifeButton.focus(); + }, 1000); + }, 500); + }, 500); + }, 500); + } + progressbar.draw(loadingPercentComplete); + }, 16); +}; + +// Start game................................................. + +game.start(); diff --git a/canvas/ch10/example-10.1/example.html b/canvas/ch10/example-10.1/example.html new file mode 100644 index 0000000..ad014af --- /dev/null +++ b/canvas/ch10/example-10.1/example.html @@ -0,0 +1,82 @@ + + + + + Rounded Rectangles + + + + + +
+
+ Width: +
+ + Height: +
+ +
+ + + + + diff --git a/canvas/ch10/example-10.1/example.js b/canvas/ch10/example-10.1/example.js new file mode 100644 index 0000000..b3eebe1 --- /dev/null +++ b/canvas/ch10/example-10.1/example.js @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var widthRange = document.getElementById('widthRange'), + heightRange = document.getElementById('heightRange'), + roundedRectangle = new COREHTML5.RoundedRectangle( + 'rgba(0, 0, 0, 0.2)', 'darkgoldenrod', + widthRange.value, heightRange.value); + +function resize() { + roundedRectangle.horizontalSizePercent = widthRange.value/100; + roundedRectangle.verticalSizePercent = heightRange.value/100; + + roundedRectangle.resize(roundedRectangle.domElement.offsetWidth, + roundedRectangle.domElement.offsetHeight); + + roundedRectangle.erase(); + roundedRectangle.draw(); +} + +widthRange.onchange = resize; +heightRange.onchange = resize; + +roundedRectangle.appendTo(document.getElementById('roundedRectangleDiv')); +roundedRectangle.draw(); diff --git a/canvas/ch10/example-10.10/example.html b/canvas/ch10/example-10.10/example.html new file mode 100644 index 0000000..501b857 --- /dev/null +++ b/canvas/ch10/example-10.10/example.html @@ -0,0 +1,133 @@ + + + + + + Panning Images + + + + + + +
+ 0 +
+ + 0 +
+
+ + + Canvas not supported + + + + + + + + diff --git a/canvas/ch10/example-10.10/example.js b/canvas/ch10/example-10.10/example.js new file mode 100644 index 0000000..6c195a4 --- /dev/null +++ b/canvas/ch10/example-10.10/example.js @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var context = document.getElementById('canvas').getContext('2d'), + image = new Image(), + + alphaSpan = document.getElementById('alphaSpan'), + sizeSpan = document.getElementById('sizeSpan'), + + sizeSlider = new COREHTML5.Slider('blue', 'cornflowerblue', + 0.85, // knob percent + 90, // take up % of width + 50), // take up % of height + + alphaSlider = new COREHTML5.Slider('blue', 'cornflowerblue', + 0.50, // knob percent + 90, // take up % of width + 50), // take up % of height + + pan = new COREHTML5.Pan(context.canvas, image), + e = pan.domElement, + + ALPHA_MAX = 1.0, + SIZE_MAX = 12; + +// Event Handlers..................................................... + +sizeSlider.addChangeListener(function (e) { + var size = (parseFloat(sizeSlider.knobPercent) * 12); + size = size < 2 ? 2 : size; + sizeSpan.innerHTML = size.toFixed(1) + '%'; + + pan.imageContext.setTransform(1,0,0,1,0,0); // identity matrix + pan.viewportPercent = size; + + pan.erase(); + pan.initialize(); + pan.draw(); +}); + +alphaSlider.addChangeListener(function (e) { + alphaSpan.innerHTML = + parseFloat(alphaSlider.knobPercent * 100).toFixed(0) + '%'; + alphaSpan.style.opacity = parseFloat(alphaSlider.knobPercent); + pan.panCanvasAlpha = alphaSlider.knobPercent; + pan.erase(); + pan.draw(); +}); + +// Initialization.................................................... + +image.src = 'pencilsAndBrush.jpg'; +document.getElementById('body').appendChild(e); +e.className = 'pan'; + +alphaSlider.appendTo('alphaSliderDiv'); +sizeSlider.appendTo('sizeSliderDiv'); + +pan.viewportPercent = sizeSlider.knobPercent * SIZE_MAX; +pan.panCanvasAlpha = alphaSlider.knobPercent * ALPHA_MAX; + +sizeSpan.innerHTML = pan.viewportPercent.toFixed(0) + '%'; +alphaSpan.innerHTML = (pan.panCanvasAlpha * 100).toFixed(0) + '%'; + +alphaSlider.draw(); +sizeSlider.draw(); diff --git a/canvas/ch10/example-10.10/pencilsAndBrush.jpg b/canvas/ch10/example-10.10/pencilsAndBrush.jpg new file mode 100644 index 0000000..f12369b Binary files /dev/null and b/canvas/ch10/example-10.10/pencilsAndBrush.jpg differ diff --git a/canvas/ch10/example-10.4/example.html b/canvas/ch10/example-10.4/example.html new file mode 100644 index 0000000..cc9ba5a --- /dev/null +++ b/canvas/ch10/example-10.4/example.html @@ -0,0 +1,71 @@ + + + + + Progress bars + + + + + + + + +
+ + + + + + + diff --git a/canvas/ch10/example-10.4/example.js b/canvas/ch10/example-10.4/example.js new file mode 100644 index 0000000..b6de1aa --- /dev/null +++ b/canvas/ch10/example-10.4/example.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var startButton = document.getElementById('startButton'), + loadingSpan = document.getElementById('loadingSpan'), + progressbar = new COREHTML5.Progressbar( + 'rgba(0, 0, 0, 0.2)', 'teal', 90, 70), + percentComplete = 0; + +progressbar.appendTo(document.getElementById('progressbarDiv')); + +startButton.onclick = function (e) { + loadingSpan.style.display = 'inline'; + startButton.style.display = 'none'; + + percentComplete += 1.0; + + if (percentComplete > 100) { + percentComplete = 0; + loadingSpan.style.display = 'none'; + startButton.style.display = 'inline'; + } + else { + progressbar.erase(); + progressbar.draw(percentComplete); + requestNextAnimationFrame(startButton.onclick); + } +}; diff --git a/canvas/ch10/example-10.7/example.html b/canvas/ch10/example-10.7/example.html new file mode 100644 index 0000000..067253e --- /dev/null +++ b/canvas/ch10/example-10.7/example.html @@ -0,0 +1,96 @@ + + + + + Sliders + + + + + +
+
+
+
+ + + Canvas not supported + + + + + + + diff --git a/canvas/ch10/example-10.7/example.js b/canvas/ch10/example-10.7/example.js new file mode 100644 index 0000000..12b965c --- /dev/null +++ b/canvas/ch10/example-10.7/example.js @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var colorPatchContext = document.getElementById('colorPatchCanvas'). + getContext('2d'), + + redSlider = new COREHTML5.Slider('rgb(0,0,0)', + 'rgba(255,0,0,0.8)', 0), + + blueSlider = new COREHTML5.Slider('rgb(0,0,0)', + 'rgba(0,0,255,0.8)', 1.0), + + greenSlider = new COREHTML5.Slider('rgb(0,0,0)', + 'rgba(0,255,0,0.8)', 0.25), + + alphaSlider = new COREHTML5.Slider('rgb(0,0,0)', + 'rgba(255,255,255,0.8)', 0.5); + +redSlider.appendTo('redSliderDiv'); +blueSlider.appendTo('blueSliderDiv'); +greenSlider.appendTo('greenSliderDiv'); +alphaSlider.appendTo('alphaSliderDiv'); + +// Functions.......................................................... + +function updateColor() { + var alpha = new Number((alphaSlider.knobPercent).toFixed(2)); + var color = 'rgba(' + + parseInt(redSlider.knobPercent * 255) + ',' + + parseInt(greenSlider.knobPercent * 255) + ',' + + parseInt(blueSlider.knobPercent * 255) + ',' + + alpha + ')'; + + colorPatchContext.fillStyle = color; + + colorPatchContext.clearRect(0,0,colorPatchContext.canvas.width, + colorPatchContext.canvas.height); + + colorPatchContext.fillRect(0,0,colorPatchContext.canvas.width, + colorPatchContext.canvas.height); + + colorPatchContext.font = '18px Arial'; + colorPatchContext.fillStyle = 'white'; + colorPatchContext.fillText(color, 10, 40); + + alpha = (alpha + 0.2 > 1.0) ? 1.0 : alpha + 0.2; + alphaSlider.opacity = alpha; +} + +// Event Handlers..................................................... + +redSlider.addChangeListener( function() { + updateColor(); + redSlider.fillStyle = 'rgb(' + + (redSlider.knobPercent * 255).toFixed(0) + ', 0, 0)'; +}); + +greenSlider.addChangeListener( function() { + updateColor(); + greenSlider.fillStyle = 'rgb(0, ' + + (greenSlider.knobPercent * 255).toFixed(0) + ', 0)'; +}); + +blueSlider.addChangeListener( function () { + updateColor(); + blueSlider.fillStyle = 'rgb(0, 0, ' + + (blueSlider.knobPercent * 255).toFixed(0) + ')'; +}); + +alphaSlider.addChangeListener( function() { + updateColor(); + alphaSlider.fillStyle = 'rgba(255, 255, 255, ' + + (alphaSlider.knobPercent * 255).toFixed(0) + ')'; + + alphaSlider.opacity = alphaSlider.knobPercent; +}); + +// Initialization..................................................... + +redSlider.fillStyle = 'rgb(' + + (redSlider.knobPercent * 255).toFixed(0) + ', 0, 0)'; + +greenSlider.fillStyle = 'rgb(0, ' + + (greenSlider.knobPercent * 255).toFixed(0) + ', 0)'; + +blueSlider.fillStyle = 'rgb(0, 0, ' + + (blueSlider.knobPercent * 255).toFixed(0) + ')'; + +alphaSlider.fillStyle = 'rgba(255, 255, 255, ' + + (alphaSlider.knobPercent * 255).toFixed(0) + ')'; + +alphaSlider.opacity = alphaSlider.knobPercent; + +alphaSlider.draw(); +redSlider.draw(); +greenSlider.draw(); +blueSlider.draw(); diff --git a/canvas/ch11/example-11.1/example.html b/canvas/ch11/example-11.1/example.html new file mode 100644 index 0000000..5d47fba --- /dev/null +++ b/canvas/ch11/example-11.1/example.html @@ -0,0 +1,58 @@ + + + + + + The Mobile Viewport: Element is 500px wide by 50px high + + + + + + +
Viewport width: 500
+ + diff --git a/canvas/ch11/example-11.1/example.js b/canvas/ch11/example-11.1/example.js new file mode 100644 index 0000000..e69de29 diff --git a/canvas/ch11/example-11.3/example.html b/canvas/ch11/example-11.3/example.html new file mode 100644 index 0000000..9f220b8 --- /dev/null +++ b/canvas/ch11/example-11.3/example.html @@ -0,0 +1,242 @@ + + + + + + Magnifying Glass + + + + + + + + + + + + + + + + + + + + + + +
+
+

Instructions

+

If you run this application directly from your filesystem, as opposed to over the internet, you must circumvent JavaScript's same-domain policy to manipulate the application's image.

+

You do that differently for different browsers; for example, Chrome has a command line argument --allow-file-access-from-files, that relaxes the same-domain policy so the application can manipulate the image. See your browser's documentation for more details.

+ + Okay + Don't show this again +
+ +
+ 1.5 +
+ + + Canvas not supported + +
+
+ + + Canvas not supported + +
+ + + + + + diff --git a/canvas/ch11/example-11.3/example.js b/canvas/ch11/example-11.3/example.js new file mode 100644 index 0000000..ad41456 --- /dev/null +++ b/canvas/ch11/example-11.3/example.js @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var canvas = document.getElementById('canvas'), + context = canvas.getContext('2d'), + + image = new Image(), + imageData = null, + dragging = false, + + instructions = document.getElementById('instructions'), + instructionsOkay = document.getElementById('instructions-okay'), + instructionsNoMore = document.getElementById('instructions-nomore'), + + glassSizeCanvas = document.getElementById('glassSizeCanvas'), + glassSizeContext = glassSizeCanvas.getContext('2d'), + + MAXIMUM_SCALE = 4.0, + scaleOutput = document.getElementById('scaleOutput'), + + magnifyingGlassRadius = 120, + magnificationScale = scaleOutput.innerHTML, + magnifyRectangle = {}, + + MAX_GLASS_RADIUS = 350, + + magnifyingGlassX = 512,//canvas.width/2, + magnifyingGlassY = 340,//canvas.height/2, + + magnifyZoomSlider = new COREHTML5.Slider('black', 'rgb(52, 72, 35)', 0.25, // knob percent + 90, // take up % of width + 55), // take up % of height + + glassSlider = new COREHTML5.Slider('black', 'rgb(52, 72, 35)', 0.50, 90, 55), + + animating = false, + animationLoop = null, + + mousedown = null, + mouseup = null, + + canvasRatio = canvas.height / canvas.width, + pinchRatio; + +// Functions................................................... + +function windowToCanvas(x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +}; + +function calculateMagnifyRectangle(mouse) { + var top, + left, + bottom, + right; + + magnifyRectangle.x = mouse.x - magnifyingGlassRadius; + magnifyRectangle.y = mouse.y - magnifyingGlassRadius; + magnifyRectangle.width = magnifyingGlassRadius*2 + 2*context.lineWidth; + magnifyRectangle.height = magnifyingGlassRadius*2 + 2*context.lineWidth; + + top = magnifyRectangle.y; + left = magnifyRectangle.x; + bottom = magnifyRectangle.y + magnifyRectangle.height; + right = magnifyRectangle.x + magnifyRectangle.width; + + if (left < 0) { + magnifyRectangle.width += left; + magnifyRectangle.x = 0; + } + else if (right > canvas.width) { + magnifyRectangle.width -= right - canvas.width; + } + + if (top < 0) { + magnifyRectangle.height += magnifyRectangle.y; + magnifyRectangle.y = 0; + } + else if (bottom > canvas.height) { + magnifyRectangle.height -= bottom - canvas.height; + } +} + +function setClip() { + context.beginPath(); + context.arc(magnifyingGlassX, magnifyingGlassY, + magnifyingGlassRadius, 0, Math.PI*2, false); + + context.clip(); +} + +function drawMagnifyingGlassCircle(mouse) { + var gradientThickness = this.magnifyingGlassRadius / 7; + + gradientThickness = gradientThickness < 10 ? 10 : gradientThickness; + gradientThickness = gradientThickness > 40 ? 40 : gradientThickness; + + gradientThickness = 10; + this.context.save(); + this.context.lineWidth = gradientThickness; + this.context.strokeStyle = 'rgb(0, 0, 255, 0.3)'; + + this.context.beginPath(); + this.context.arc(mouse.x, mouse.y, + this.magnifyingGlassRadius, 0, Math.PI*2, false); + this.context.clip(); + + var gradient = this.context.createRadialGradient( + mouse.x, mouse.y, this.magnifyingGlassRadius-gradientThickness, + mouse.x, mouse.y, this.magnifyingGlassRadius); + gradient.addColorStop(0, 'rgba(0,0,0,0.2)'); + gradient.addColorStop(0.80, 'silver'); + gradient.addColorStop(0.90, 'silver'); + gradient.addColorStop(1.0, 'rgba(150,150,150,0.9)'); + + this.context.shadowColor = 'rgba(52, 72, 35, 1.0)'; + this.context.shadowOffsetX = 2; + this.context.shadowOffsetY = 2; + this.context.shadowBlur = 20; + + this.context.strokeStyle = gradient; + this.context.stroke(); + + this.context.beginPath(); + this.context.arc(mouse.x, mouse.y, + this.magnifyingGlassRadius-gradientThickness/2, 0, Math.PI*2, false); + this.context.clip(); + + this.context.lineWidth = gradientThickness; + this.context.strokeStyle = 'rgba(0,0,0,0.06)'; + this.context.stroke(); + + this.context.restore(); +}; + +function drawMagnifyingGlass(mouse) { + var scaledMagnifyRectangle; + + + if (window.netscape) { + if (netscape.security.PrivilegeManager) + netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); + } + magnifyingGlassX = mouse.x; + magnifyingGlassY = mouse.y; + + calculateMagnifyRectangle(mouse); + + imageData = context.getImageData(magnifyRectangle.x, + magnifyRectangle.y, + magnifyRectangle.width, + magnifyRectangle.height); + context.save(); + + scaledMagnifyRectangle = { + width: magnifyRectangle.width * magnificationScale, + height: magnifyRectangle.height * magnificationScale + }; + + setClip(); + + context.drawImage(canvas, + magnifyRectangle.x, magnifyRectangle.y, + magnifyRectangle.width, magnifyRectangle.height, + + magnifyRectangle.x + magnifyRectangle.width/2 - + scaledMagnifyRectangle.width/2, + + magnifyRectangle.y + magnifyRectangle.height/2 - + scaledMagnifyRectangle.height/2, + + scaledMagnifyRectangle.width, + scaledMagnifyRectangle.height); + + context.restore(); + + drawMagnifyingGlassCircle(mouse); +} + +function eraseMagnifyingGlass() { // Called when the mouse moves + if (imageData != null) { + context.putImageData(imageData, + magnifyRectangle.x, + magnifyRectangle.y); + } +} + +function drawGlassIcon(context, radius) { + context.save(); + context.clearRect(0,0,context.canvas.width, + context.canvas.height); + + context.shadowColor = 'rgba(52, 72, 35, 0.5)'; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.shadowBlur = 2; + + context.beginPath(); + + context.translate(context.canvas.width/2, + context.canvas.height/2); + + context.beginPath(); + context.lineWidth = 2.5; + context.arc(0, 0, radius+3, 0, Math.PI*2, false); + context.strokeStyle = 'rgb(52, 72, 35)'; + context.stroke(); + + context.beginPath(); + context.lineWidth = 0.5; + context.strokeStyle = 'rgba(255,255,255,0.6)'; + context.arc(0, 0, radius+6, 0, Math.PI*2, false); + context.stroke(); + + context.restore(); + }; + +function drawMagnificationText(value, percent) { + scaleOutput.innerHTML = value; + percent = percent < 0.35 ? 0.35 : percent; + scaleOutput.style.fontSize = percent*MAXIMUM_SCALE/2 + 'em'; +} + +function updateMagnifyingGlass() { + eraseMagnifyingGlass(); + drawMagnifyingGlass({ x: magnifyingGlassX, y: magnifyingGlassY }); +} + +function step(time, lastTime, mouse, speed) { + var elapsedTime = time - lastTime, + nextLeft = mouse.x - magnifyingGlassRadius + speed.vx*(elapsedTime/10), + nextTop = mouse.y - magnifyingGlassRadius + speed.vy*(elapsedTime/10), + nextRight = nextLeft + magnifyingGlassRadius*2, + nextBottom = nextTop + magnifyingGlassRadius*2; + + eraseMagnifyingGlass(); + + if (nextLeft < 0) { + speed.vx = -speed.vx; + mouse.x = magnifyingGlassRadius; + } + else if (nextRight > canvas.width) { + speed.vx = -speed.vx; + mouse.x = canvas.width - magnifyingGlassRadius; + } + + if (nextTop < 0) { + speed.vy = -speed.vy; + mouse.y = magnifyingGlassRadius; + } + else if (nextBottom > canvas.height) { + speed.vy = -speed.vy; + mouse.y = canvas.height - magnifyingGlassRadius; + } + + mouse.x += speed.vx*(elapsedTime/10); + mouse.y += speed.vy*(elapsedTime/10); + + drawMagnifyingGlass(mouse); +} +function animate(mouse, speed) { + var time, lastTime = 0, elapsedTime; + animating = true; + + if (lastTime === 0) { + lastTime = +new Date; + } + + animationLoop = setInterval(function() { + var time = + new Date; + step(time, lastTime, mouse, speed); + lastTime = time; + }, 1000/60); +} + +function didThrow() { + var elapsedTime = mouseup.time - mousedown.time; + var elapsedMotion = Math.abs(mouseup.x - mousedown.x) + + Math.abs(mouseup.y - mousedown.y); + return false; //(elapsedMotion / elapsedTime * 10) > 3; +} + +// Touch Event Handlers........................................ + +function isPinching (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + return changed === 1 || changed === 2 && touching === 2; +} + +function isDragging (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + return changed === 1 && touching === 1; +} + +canvas.ontouchstart = function (e) { + var changed = e.changedTouches.length, + touching = e.touches.length; + + e.preventDefault(e); + + if (isDragging(e)) { + mouseDownOrTouchStart(windowToCanvas(e.pageX, e.pageY)); + } + else if (isPinching(e)) { + var touch1 = e.touches.item(0), + touch2 = e.touches.item(1), + point1 = windowToCanvas(touch1.pageX, touch1.pageY), + point2 = windowToCanvas(touch2.pageX, touch2.pageY); + + distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.x - point1.x, 2)); + pinchRatio = magnificationScale / distance; + } +}; + +canvas.ontouchmove = function (e) { + var changed = e.changedTouches.length, + touching = e.touches.length, + distance, touch1, touch2; + + e.preventDefault(e); + + if (isDragging(e)) { + mouseMoveOrTouchMove(windowToCanvas(e.pageX, e.pageY)); + } + else if (isPinching(e)) { + var touch1 = e.touches.item(0), + touch2 = e.touches.item(1), + point1 = windowToCanvas(touch1.pageX, touch1.pageY), + point2 = windowToCanvas(touch2.pageX, touch2.pageY), + scale; + + distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.x - point1.x, 2)); + scale = pinchRatio * distance; + + if (scale > 1 && scale < 3) { + magnificationScale = parseFloat(pinchRatio * distance).toFixed(2); + draw(); + } + } +}; + +canvas.ontouchend = function (e) { + e.preventDefault(e); + mouseUpOrTouchEnd(windowToCanvas(e.pageX, e.pageY)); +}; + +// Mouse Event Handlers........................................ + +canvas.onmousedown = function (e) { + e.preventDefault(e); + mouseDownOrTouchStart(windowToCanvas(e.clientX, e.clientY)); +}; + +canvas.onmousemove = function (e) { + e.preventDefault(e); + mouseMoveOrTouchMove(windowToCanvas(e.clientX, e.clientY)); +}; + +canvas.onmouseup = function (e) { + e.preventDefault(e); + mouseUpOrTouchEnd(windowToCanvas(e.clientX, e.clientY)); +}; + +function mouseDownOrTouchStart(mouse) { + mousedown = { x: mouse.x, y: mouse.y, time: (new Date).getTime() }; + + if (animating) { + animating = false; + clearInterval(animationLoop); + eraseMagnifyingGlass(); + } + else { + dragging = true; + context.save(); + } +}; + +function mouseMoveOrTouchMove(mouse) { + if (dragging) { + eraseMagnifyingGlass(); + drawMagnifyingGlass(mouse); + } +}; + +function mouseUpOrTouchEnd(mouse) { + mouseup = { x: mouse.x, y: mouse.y, time: (new Date).getTime() }; + + if (dragging) { + if (didThrow()) { + velocityX = (mouseup.x-mousedown.x)/100; + velocityY = (mouseup.y-mousedown.y)/100; + animate(mouse, { vx: velocityX, vy: velocityY }); + } + else { + //eraseMagnifyingGlass(); + } + } + dragging = false; +}; + +// Slider Event Handlers....................................... + +magnifyZoomSlider.addChangeListener( function(e) { + var maxRadius = (glassSizeCanvas.width/2-7); + percent = magnifyZoomSlider.knobPercent, + value = parseFloat(1 + percent * 2).toFixed(2); + + drawMagnificationText(value, percent); + magnificationScale = value; + updateMagnifyingGlass(); +}); + +glassSlider.addChangeListener( function(e) { + var maxRadius = glassSizeCanvas.width/2-5, + percent = parseFloat(glassSlider.knobPercent), + value = 25 + new Number((percent * 175).toFixed(0)); + + magnifyingGlassRadius = value + drawGlassIcon(glassSizeContext, maxRadius * percent); + updateMagnifyingGlass(); +}); + +// Initialization.............................................. + +context.fillStyle = 'cornflowerblue'; +context.strokeStyle = 'rgba(250, 250, 0, 0.5)'; +context.shadowColor = 'rgba(0, 0, 0, 0.5)'; +context.shadowOffsetX = 10; +context.shadowOffsetY = 10; +context.shadowBlur = 20; + +function draw() { + var maxRadius = (glassSizeCanvas.width/2-7), + percent = parseFloat(glassSlider.knobPercent); + + context.drawImage(image, 0, 0, canvas.width, canvas.height); + drawGlassIcon(glassSizeContext, maxRadius * 0.5); + drawMagnificationText(magnificationScale, percent); + drawMagnifyingGlass({ x: magnifyingGlassX, y: magnifyingGlassY }); +} + +image.src = '../../shared/images/camp.png'; +image.onload = function(e) { + draw(); +}; + +drawGlassIcon(glassSizeContext, (glassSizeCanvas.width/2-7)/2 ); + +canvas.addEventListener('dragenter', function (e) { + e.preventDefault(); + e.dataTransfer.effectAllowed = 'copy'; +}, false); + +canvas.addEventListener('dragover', function (e) { + e.preventDefault(); +}, false); + +window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + +canvas.addEventListener('drop', function (e) { + var file = e.dataTransfer.files[0]; + + window.requestFileSystem(window.TEMPORARY, 5*1024*1024, + function (fs) { + fs.root.getFile(file.name, {create: true}, + function (fileEntry) { + fileEntry.createWriter( function (writer) { + writer.write(file); + }); + image.src = fileEntry.toURL(); + }, + + function (e) { + alert(e.code); + } + ); + }, + + function (e) { + alert(e.code); + } + ); +}, false); + +magnifyZoomSlider.appendTo('magnificationSliderDiv'); +glassSlider.appendTo('glassSizeSliderDiv'); + +magnifyZoomSlider.draw(); +glassSlider.draw(); + +if (window.matchMedia && screen.width <= 1024) { + var m = window.matchMedia("(orientation:portrait)"), + lw = 0, lh = 0, lr = 0; + + function listener (mql) { + var cr = canvas.getBoundingClientRect(); + + if (mql.matches) { // portrait + // Save landscape size to reset later + lw = canvas.width; + lh = canvas.height; + lr = magnifyingGlassRadius; + + // Resize for portrait + canvas.width = screen.width - 2*cr.left; + canvas.height = canvas.width*canvasRatio; + + magnifyingGlassRadius *= (canvas.width + canvas.height) / (lw + lh); + } + else if (lw !== 0 && lh !== 0) { // landscape + // Reset landscape size + canvas.width = lw; + canvas.height = lh; + + magnifyingGlassRadius = lr; + } + + // Setting canvas width and height resets and + // erases the canvas, making a redraw necessary + + draw(); + } + + m.addListener( listener ); +} + +instructionsOkay.onclick = function (e) { + instructions.style.display = 'none'; +}; + +instructionsNoMore.onclick = function (e) { + instructions.style.display = 'none'; + localStorage['corehtml5:magnifying-glass:instructions']= 'no'; +}; + + +if (localStorage['corehtml5:magnifying-glass:instructions'] === 'no') { + instructions.style.display = 'none'; +} diff --git a/canvas/ch11/example-11.3/icon-ipad.png b/canvas/ch11/example-11.3/icon-ipad.png new file mode 100644 index 0000000..3c23200 Binary files /dev/null and b/canvas/ch11/example-11.3/icon-ipad.png differ diff --git a/canvas/ch11/example-11.3/startup-iPad-landscape.png b/canvas/ch11/example-11.3/startup-iPad-landscape.png new file mode 100644 index 0000000..b564942 Binary files /dev/null and b/canvas/ch11/example-11.3/startup-iPad-landscape.png differ diff --git a/canvas/ch11/example-11.3/startup-iPad-portrait.png b/canvas/ch11/example-11.3/startup-iPad-portrait.png new file mode 100644 index 0000000..836dea6 Binary files /dev/null and b/canvas/ch11/example-11.3/startup-iPad-portrait.png differ diff --git a/canvas/ch11/example-11.3/startup-iphone.png b/canvas/ch11/example-11.3/startup-iphone.png new file mode 100644 index 0000000..bd6300c Binary files /dev/null and b/canvas/ch11/example-11.3/startup-iphone.png differ diff --git a/canvas/ch11/example-11.3/startup-iphone4.png b/canvas/ch11/example-11.3/startup-iphone4.png new file mode 100644 index 0000000..f3c30f5 Binary files /dev/null and b/canvas/ch11/example-11.3/startup-iphone4.png differ diff --git a/canvas/ch11/example-11.5/example.html b/canvas/ch11/example-11.5/example.html new file mode 100644 index 0000000..cc94438 --- /dev/null +++ b/canvas/ch11/example-11.5/example.html @@ -0,0 +1,263 @@ + + + + + + Paint + + + + + + + + + + + + + + + + + + + + + +
+ Stroke: + + Fill: + + Line width: + + + + Drag the image to your desktop (or Right-click to save to disk) +
+ + + + + Canvas not supported + + + + Canvas not supported + + +
+ +
+

+ The yellow circle is a control point for the curve. Drag that + circle around to change the shape of the curve. +

+ + + +
+ + + + + diff --git a/canvas/ch11/example-11.5/example.js b/canvas/ch11/example-11.5/example.js new file mode 100644 index 0000000..ec3a0b8 --- /dev/null +++ b/canvas/ch11/example-11.5/example.js @@ -0,0 +1,1141 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var iconCanvas = document.getElementById('iconCanvas'), + drawingCanvas = document.getElementById('drawingCanvas'), + drawingContext = drawingCanvas.getContext('2d'), + backgroundContext = document.createElement('canvas').getContext('2d'), + iconContext = iconCanvas.getContext('2d'), + strokeStyleSelect = document.getElementById('strokeStyleSelect'), + fillStyleSelect = document.getElementById('fillStyleSelect'), + lineWidthSelect = document.getElementById('lineWidthSelect'), + eraseAllButton = document.getElementById('eraseAllButton'), + snapshotButton = document.getElementById('snapshotButton'), + controls = document.getElementById('controls'), + curveInstructions = document.getElementById('curveInstructions'), + curveInstructionsOkayButton = document.getElementById('curveInstructionsOkayButton'), + curveInstructionsNoMoreButton = document.getElementById('curveInstructionsNoMoreButton'), + + showCurveInstructions = true, + + drawingSurfaceImageData, + rubberbandW, + rubberbandH, + rubberbandUlhc = {}, + + dragging = false, + mousedown = {}, + lastRect = {}, + lastX, lastY, + + controlPoint = {}, + editingCurve = false, + draggingControlPoint = false, + curveStart = {}, + curveEnd = {}, + + doFill = false, + selectedRect = null, + selectedFunction, + + editingText = false, + currentText, + + CONTROL_POINT_RADIUS = 20, + CONTROL_POINT_FILL_STYLE = 'rgba(255,255,0,0.5)', + CONTROL_POINT_STROKE_STYLE = 'rgba(0, 0, 255, 0.8)', + + RUBBERBAND_LINE_WIDTH = 1, + RUBBERBAND_STROKE_STYLE = 'green', + + GRID_HORIZONTAL_SPACING = 10, + GRID_VERTICAL_SPACING = 10, + GRID_LINE_COLOR = 'rgb(0, 0, 200)', + + ERASER_ICON_GRID_COLOR = 'rgb(0, 0, 200)', + ERASER_ICON_CIRCLE_COLOR = 'rgba(100, 140, 200, 0.5)', + ERASER_ICON_RADIUS = 20, + + SLINKY_LINE_WIDTH = 1, + SLINKY_SHADOW_STYLE = 'rgba(0,0,0,0.2)', + SLINKY_SHADOW_OFFSET = -5, + SLINKY_SHADOW_BLUR = 20, + SLINKY_RADIUS = 60, + + ERASER_LINE_WIDTH = 1, + ERASER_SHADOW_STYLE = 'blue', + ERASER_STROKE_STYLE = 'rgba(0,0,255,0.6)', + ERASER_SHADOW_OFFSET = -5, + ERASER_SHADOW_BLUR = 20, + ERASER_RADIUS = 40, + + SHADOW_COLOR = 'rgba(0,0,0,0.7)', + + ICON_BACKGROUND_STYLE = '#eeeeee', + ICON_BORDER_STROKE_STYLE = 'rgba(100, 140, 230, 0.5)', + ICON_STROKE_STYLE = 'rgb(100, 140, 230)', + ICON_FILL_STYLE = '#dddddd', + + TEXT_ICON_FILL_STYLE = 'rgba(100, 140, 230, 0.5)', + TEXT_ICON_TEXT = 'T', + + CIRCLE_ICON_RADIUS = 20, + + ICON_RECTANGLES = [ + { x: 13.5, y: 18.5, w: 48, h: 48 }, + { x: 13.5, y: 78.5, w: 48, h: 48 }, + { x: 13.5, y: 138.5, w: 48, h: 48 }, + { x: 13.5, y: 198.5, w: 48, h: 48 }, + { x: 13.5, y: 258.5, w: 48, h: 48 }, + { x: 13.5, y: 318.5, w: 48, h: 48 }, + { x: 13.5, y: 378.5, w: 48, h: 48 }, + { x: 13.5, y: 438.5, w: 48, h: 48 }, + { x: 13.5, y: 508.5, w: 48, h: 48 } + ], + + LINE_ICON = 0, + RECTANGLE_ICON = 1, + CIRCLE_ICON = 2, + OPEN_PATH_ICON = 3, + CLOSED_PATH_ICON = 4, + CURVE_ICON = 5, + TEXT_ICON = 6, + SLINKY_ICON = 7, + ERASER_ICON = 8, + + keyboard = new COREHTML5.Keyboard(); + +// Grid.......................................................... + +function drawGrid(context, color, stepx, stepy) { + context.save() + + context.strokeStyle = color; + context.fillStyle = '#ffffff'; + context.lineWidth = 0.5; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + context.globalAlpha = 0.1; + + context.beginPath(); + for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) { + context.moveTo(i, 0); + context.lineTo(i, context.canvas.height); + } + context.stroke(); + + context.beginPath(); + for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) { + context.moveTo(0, i); + context.lineTo(context.canvas.width, i); + } + context.stroke(); + + context.restore(); +} + +// Icons......................................................... + +function drawLineIcon(rect) { + iconContext.beginPath(); + iconContext.moveTo(rect.x + 5, rect.y + 5); + iconContext.lineTo(rect.x + rect.w - 5, rect.y + rect.h - 5); + iconContext.stroke(); +} + +function drawRectIcon(rect) { + fillIconLowerRight(rect); + iconContext.strokeRect(rect.x + 5, rect.y + 5, + rect.w - 10, rect.h - 10); +} + +function drawCircleIcon(rect) { + var startAngle = 3*Math.PI/4, + endAngle = 7*Math.PI/4, + center = {x: rect.x + rect.w/2, y: rect.y + rect.h/2 }; + + fillIconLowerRight(rect); + + iconContext.beginPath(); + iconContext.arc(rect.x + rect.w/2, rect.y + rect.h/2, + CIRCLE_ICON_RADIUS, 0, Math.PI*2, false); + iconContext.stroke(); +} + +function drawOpenPathIcon(rect) { + iconContext.beginPath(); + drawOpenPathIconLines(rect); + iconContext.stroke(); +} + +function drawClosedPathIcon(rect) { + fillIconLowerRight(rect); + iconContext.beginPath(); + drawOpenPathIconLines(rect); + iconContext.closePath(); + iconContext.stroke(); +} + +function drawCurveIcon(rect) { + fillIconLowerRight(rect); + iconContext.beginPath(); + iconContext.beginPath(); + iconContext.moveTo(rect.x + rect.w - 10, rect.y + 5); + iconContext.quadraticCurveTo(rect.x - 10, rect.y, + rect.x + rect.w - 10, + rect.y + rect.h - 5); + iconContext.stroke(); +} + +function drawTextIcon(rect) { + var text = TEXT_ICON_TEXT; + + fillIconLowerRight(rect); + iconContext.fillStyle = TEXT_ICON_FILL_STYLE; + iconContext.fillText(text, rect.x + rect.w/2, + rect.y + rect.h/2 + 5); + iconContext.strokeText(text, rect.x + rect.w/2, + rect.y + rect.h/2 + 5); +} + +function drawSlinkyIcon(rect) { + var x, y; + + fillIconLowerRight(rect); + + iconContext.save(); + iconContext.strokeStyle = 'rgba(100, 140, 230, 0.6)'; + + for (var i=-2; i < rect.w/3 + 2; i+=1.5) { + if (i < rect.w/6) x = rect.x + rect.w/3 + i + rect.w/8; + else x = rect.x + rect.w/3 + (rect.w/3 - i) + rect.w/8; + + y = rect.y + rect.w/3 + i; + + iconContext.beginPath(); + iconContext.arc(x, y, 12, 0, Math.PI*2, false); + iconContext.stroke(); + } + iconContext.restore(); +} + +function drawEraserIcon(rect) { + var rect = ICON_RECTANGLES[ERASER_ICON]; + iconContext.save(); + + iconContext.beginPath(); + iconContext.arc(rect.x + rect.w/2, + rect.y + rect.h/2, + ERASER_ICON_RADIUS, 0, Math.PI*2, false); + + iconContext.strokeStyle = ERASER_ICON_CIRCLE_COLOR; + iconContext.stroke(); + + iconContext.clip(); // restrict drawGrid() to the circle + + drawGrid(iconContext, ERASER_ICON_GRID_COLOR, 5, 5); + + iconContext.restore(); +} + +function drawIcon(rect) { + iconContext.save(); + + iconContext.strokeStyle = ICON_BORDER_STROKE_STYLE; + iconContext.strokeRect(rect.x, rect.y, rect.w, rect.h); + iconContext.strokeStyle = ICON_STROKE_STYLE; + + if (rect.y === ICON_RECTANGLES[LINE_ICON].y) drawLineIcon(rect); + else if (rect.y === ICON_RECTANGLES[RECTANGLE_ICON].y) drawRectIcon(rect); + else if (rect.y === ICON_RECTANGLES[CIRCLE_ICON].y) drawCircleIcon(rect); + else if (rect.y === ICON_RECTANGLES[OPEN_PATH_ICON].y) drawOpenPathIcon(rect); + else if (rect.y === ICON_RECTANGLES[CLOSED_PATH_ICON].y) drawClosedPathIcon(rect, 20); + else if (rect.y === ICON_RECTANGLES[TEXT_ICON].y) drawTextIcon(rect); + else if (rect.y === ICON_RECTANGLES[CURVE_ICON].y) drawCurveIcon(rect); + else if (rect.y === ICON_RECTANGLES[ERASER_ICON].y) drawEraserIcon(rect); + else if (rect.y === ICON_RECTANGLES[SLINKY_ICON].y) drawSlinkyIcon(rect); + + iconContext.restore(); +} + +function drawIcons() { + iconContext.clearRect(0,0, iconCanvas.width, + iconCanvas.height); + + ICON_RECTANGLES.forEach(function(rect) { + iconContext.save(); + + if (selectedRect === rect) setSelectedIconShadow(); + else setIconShadow(); + + iconContext.fillStyle = ICON_BACKGROUND_STYLE; + iconContext.fillRect(rect.x, rect.y, rect.w, rect.h); + + iconContext.restore(); + + drawIcon(rect); + }); +} + +function drawOpenPathIconLines(rect) { + iconContext.lineTo(rect.x + 13, rect.y + 19); + iconContext.lineTo(rect.x + 15, rect.y + 17); + iconContext.lineTo(rect.x + 25, rect.y + 12); + iconContext.lineTo(rect.x + 35, rect.y + 13); + iconContext.lineTo(rect.x + 38, rect.y + 15); + iconContext.lineTo(rect.x + 40, rect.y + 17); + iconContext.lineTo(rect.x + 39, rect.y + 23); + iconContext.lineTo(rect.x + 36, rect.y + 25); + iconContext.lineTo(rect.x + 32, rect.y + 27); + iconContext.lineTo(rect.x + 28, rect.y + 29); + iconContext.lineTo(rect.x + 26, rect.y + 31); + iconContext.lineTo(rect.x + 24, rect.y + 33); + iconContext.lineTo(rect.x + 22, rect.y + 35); + iconContext.lineTo(rect.x + 20, rect.y + 37); + iconContext.lineTo(rect.x + 18, rect.y + 39); + iconContext.lineTo(rect.x + 16, rect.y + 39); + iconContext.lineTo(rect.x + 13, rect.y + 36); + iconContext.lineTo(rect.x + 11, rect.y + 34); +} + +function fillIconLowerRight(rect) { + iconContext.beginPath(); + iconContext.moveTo(rect.x + rect.w, rect.y); + iconContext.lineTo(rect.x + rect.w, rect.y + rect.h); + iconContext.lineTo(rect.x, rect.y + rect.h); + iconContext.closePath(); + iconContext.fill(); +} + +function isPointInIconLowerRight(rect, x, y) { + iconContext.beginPath(); + iconContext.moveTo(rect.x + rect.w, rect.y); + iconContext.lineTo(rect.x + rect.w, rect.y + rect.h); + iconContext.lineTo(rect.x, rect.y + rect.h); + + return iconContext.isPointInPath(x, y); +} + +function getIconFunction(rect, loc) { + var action; + + if (rect.y === ICON_RECTANGLES[LINE_ICON].y) action = 'line'; + else if (rect.y === ICON_RECTANGLES[RECTANGLE_ICON].y) action = 'rectangle'; + else if (rect.y === ICON_RECTANGLES[CIRCLE_ICON].y) action = 'circle'; + else if (rect.y === ICON_RECTANGLES[OPEN_PATH_ICON].y) action = 'path'; + else if (rect.y === ICON_RECTANGLES[CLOSED_PATH_ICON].y) action = 'pathClosed'; + else if (rect.y === ICON_RECTANGLES[CURVE_ICON].y) action = 'curve'; + else if (rect.y === ICON_RECTANGLES[TEXT_ICON].y) action = 'text'; + else if (rect.y === ICON_RECTANGLES[SLINKY_ICON].y) action = 'slinky'; + else if (rect.y === ICON_RECTANGLES[ERASER_ICON].y) action = 'erase'; + + if (action === 'rectangle' || action === 'circle' || + action === 'pathClosed' || action === 'text' || + action === 'curve' || action === 'slinky') { + doFill = isPointInIconLowerRight(rect, loc.x, loc.y); + } + + return action; +} + +function setIconShadow() { + iconContext.shadowColor = SHADOW_COLOR; + iconContext.shadowOffsetX = 1; + iconContext.shadowOffsetY = 1; + iconContext.shadowBlur = 2; +} + +function setSelectedIconShadow() { + iconContext.shadowColor = SHADOW_COLOR; + iconContext.shadowOffsetX = 4; + iconContext.shadowOffsetY = 4; + iconContext.shadowBlur = 5; +} + +function selectIcon(rect) { + selectedRect = rect; + drawIcons(); +} + +// Saving/Restoring the drawing surface.......................... + +function saveDrawingSurface() { + drawingSurfaceImageData = drawingContext.getImageData(0, 0, + drawingCanvas.width, + drawingCanvas.height); +} + +function restoreDrawingSurface() { + drawingContext.putImageData(drawingSurfaceImageData, 0, 0); +} + +// Rubberbands................................................... + +function updateRubberbandRectangle(loc) { + rubberbandW = Math.abs(loc.x - mousedown.x); + rubberbandH = Math.abs(loc.y - mousedown.y); + + if (loc.x > mousedown.x) rubberbandUlhc.x = mousedown.x; + else rubberbandUlhc.x = loc.x; + + if (loc.y > mousedown.y) rubberbandUlhc.y = mousedown.y; + else rubberbandUlhc.y = loc.y; +} + +function drawRubberbandRectangle() { + drawingContext.strokeRect(rubberbandUlhc.x, + rubberbandUlhc.y, + rubberbandW, rubberbandH); +} + +function drawRubberbandLine(loc) { + drawingContext.beginPath(); + drawingContext.moveTo(mousedown.x, mousedown.y); + drawingContext.lineTo(loc.x, loc.y); + drawingContext.stroke(); +} + +function drawRubberbandCircle(loc) { + var angle = Math.atan(rubberbandH/rubberbandW); + var radius = rubberbandH / Math.sin(angle); + + if (mousedown.y === loc.y) { + radius = Math.abs(loc.x - mousedown.x); + } + + drawingContext.beginPath(); + drawingContext.arc(mousedown.x, mousedown.y, radius, 0, Math.PI*2, false); + drawingContext.stroke(); +} + +function drawRubberband(loc) { + drawingContext.save(); + + drawingContext.strokeStyle = RUBBERBAND_STROKE_STYLE; + drawingContext.lineWidth = RUBBERBAND_LINE_WIDTH; + + if (selectedFunction === 'rectangle') { + drawRubberbandRectangle(); + } + else if (selectedFunction === 'line' || + selectedFunction === 'curve') { + drawRubberbandLine(loc); + } + else if (selectedFunction === 'circle') { + drawRubberbandCircle(loc); + } + + drawingContext.restore(); +} + +// Eraser........................................................ + +function setPathForEraser() { + drawingContext.beginPath(); + drawingContext.moveTo(lastX, lastY); + drawingContext.arc(lastX, lastY, + ERASER_RADIUS + ERASER_LINE_WIDTH, + 0, Math.PI*2, false); +} + +function setSlinkyAttributes() { + drawingContext.lineWidth = lineWidthSelect.value; + drawingContext.shadowColor = strokeStyleSelect.value; + drawingContext.shadowOffsetX = SLINKY_SHADOW_OFFSET; + drawingContext.shadowOffsetY = SLINKY_SHADOW_OFFSET; + drawingContext.shadowBlur = SLINKY_SHADOW_BLUR; + drawingContext.strokeStyle = strokeStyleSelect.value; +} + +function setEraserAttributes() { + drawingContext.lineWidth = ERASER_LINE_WIDTH; + drawingContext.shadowColor = ERASER_SHADOW_STYLE; + drawingContext.shadowOffsetX = ERASER_SHADOW_OFFSET; + drawingContext.shadowOffsetY = ERASER_SHADOW_OFFSET; + drawingContext.shadowBlur = ERASER_SHADOW_BLUR; + drawingContext.strokeStyle = ERASER_STROKE_STYLE; +} + +function eraseLast() { + var x = lastX - ERASER_RADIUS-ERASER_LINE_WIDTH, + y = lastY - ERASER_RADIUS-ERASER_LINE_WIDTH, + w = ERASER_RADIUS*2+ERASER_LINE_WIDTH*2, + h = w, + cw = drawingContext.canvas.width, + ch = drawingContext.canvas.height; + + drawingContext.save(); + + setPathForEraser(); + drawingContext.clip(); + + if (x + w > cw) w = cw - x; + if (y + h > ch) h = ch - y; + + if (x < 0) { x = 0; } + if (y < 0) { y = 0; } + + drawingContext.drawImage( + backgroundContext.canvas, x, y, w, h, x, y, w, h); + + drawingContext.restore(); +} + +function drawEraser(loc) { + drawingContext.save(); + setEraserAttributes(); + + drawingContext.beginPath(); + drawingContext.arc(loc.x, loc.y, ERASER_RADIUS, + 0, Math.PI*2, false); + drawingContext.clip(); + drawingContext.stroke(); + + drawingContext.restore(); +} + +function drawSlinky(loc) { + drawingContext.save(); + setSlinkyAttributes(); + + drawingContext.beginPath(); + drawingContext.arc(loc.x, loc.y, ERASER_RADIUS, + 0, Math.PI*2, false); + drawingContext.clip(); + + drawingContext.strokeStyle = strokeStyleSelect.value; + drawingContext.stroke(); + + if (doFill) { + drawingContext.shadowColor = undefined; + drawingContext.shadowOffsetX = 0; + drawingContext.globalAlpha = 0.2; + drawingContext.fill(); + } + drawingContext.restore(); +} + +// Finish drawing lines, circles, and rectangles................. + +function finishDrawingLine(loc) { + drawingContext.beginPath(); + drawingContext.moveTo(mousedown.x, mousedown.y); + drawingContext.lineTo(loc.x, loc.y); + drawingContext.stroke(); +} + +function finishDrawingCircle(loc) { + var angle = Math.atan(rubberbandH/rubberbandW), + radius = rubberbandH / Math.sin(angle); + + if (mousedown.y === loc.y) { + radius = Math.abs(loc.x - mousedown.x); + } + + drawingContext.beginPath(); + drawingContext.arc(mousedown.x, mousedown.y, + radius, 0, Math.PI*2, false); + + if (doFill) { + drawingContext.fill(); + } + + drawingContext.stroke(); +} + +function finishDrawingRectangle() { + if (rubberbandW > 0 && rubberbandH > 0) { + if (doFill) { + drawingContext.fillRect(rubberbandUlhc.x, + rubberbandUlhc.y, + rubberbandW, rubberbandH) + } + drawingContext.strokeRect(rubberbandUlhc.x, + rubberbandUlhc.y, + rubberbandW, rubberbandH); + } +} + +// Drawing curves................................................ + +function drawControlPoint() { + drawingContext.save(); + + drawingContext.strokeStyle = CONTROL_POINT_STROKE_STYLE; + drawingContext.fillStyle = CONTROL_POINT_FILL_STYLE; + drawingContext.lineWidth = 1.0; + + drawingContext.beginPath(); + drawingContext.arc(controlPoint.x, controlPoint.y, + CONTROL_POINT_RADIUS, 0, Math.PI*2, false); + drawingContext.stroke(); + drawingContext.fill(); + + drawingContext.restore(); +} + +function startEditingCurve(loc) { + if (loc.x != mousedown.x || loc.y != mousedown.y) { + drawingCanvas.style.cursor = 'pointer'; + + curveStart.x = mousedown.x; + curveStart.y = mousedown.y; + + curveEnd.x = loc.x; + curveEnd.y = loc.y; + + controlPoint.x = (curveStart.x + curveEnd.x)/2; + controlPoint.y = (curveStart.y + curveEnd.y)/2; + + drawControlPoint(); + + editingCurve = true; + + if (showCurveInstructions) + curveInstructions.style.display = 'inline'; + } +} + +function drawCurve() { + drawingContext.beginPath(); + drawingContext.moveTo(curveStart.x, curveStart.y); + drawingContext.quadraticCurveTo(controlPoint.x, controlPoint.y, + curveEnd.x, curveEnd.y); + drawingContext.stroke(); +} + +function finishDrawingCurve() { + drawingCanvas.style.cursor = 'crosshair'; + restoreDrawingSurface(); + drawCurve(); + + if (doFill) { + drawingContext.fill(); + } +} + +// Guidewires.................................................... + +function drawHorizontalLine (y) { + drawingContext.beginPath(); + drawingContext.moveTo(0, y+0.5); + drawingContext.lineTo(drawingCanvas.width, y+0.5); + drawingContext.stroke(); +} + +function drawVerticalLine (x) { + drawingContext.beginPath(); + drawingContext.moveTo(x+0.5, 0); + drawingContext.lineTo(x+0.5, drawingCanvas.height); + drawingContext.stroke(); +} + +function drawGuidewires(x, y) { + drawingContext.save(); + drawingContext.strokeStyle = 'rgba(0,0,230,0.4)'; + drawingContext.lineWidth = 0.5; + drawVerticalLine(x); + drawHorizontalLine(y); + drawingContext.restore(); +} + +// Keyboard...................................................... + +function showKeyboard() { + var keyboardElement = document.getElementById('keyboard'); + + keyboardElement.style.height = '370px'; + keyboardElement.style.top = '375px'; + keyboardElement.style.border = 'thin inset rgba(0,0,0,0.5)'; + keyboardElement.style.borderRadius = '20px'; + + keyboard.resize(1000, 368); + keyboard.translucent = mousedown.y > drawingCanvas.height/2; + keyboard.draw(); +} + +function hideKeyboard() { + var keyboardElement = document.getElementById('keyboard'); + + keyboardElement.style.height = '0px'; + keyboardElement.style.top = '0px'; + keyboardElement.style.border = ''; + keyboardElement.style.borderRadius = ''; + + keyboard.resize(1000, 0); +} + +// Event handling functions...................................... + +function windowToCanvas(canvas, x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; +} + +function mouseDownOrTouchStartInControlCanvas(loc) { + if (editingText) { + editingText = false; + eraseTextCursor(); + hideKeyboard(); + } + else if (editingCurve) { + editingCurve = false; + restoreDrawingSurface(); + } + + ICON_RECTANGLES.forEach(function(rect) { + iconContext.beginPath(); + + iconContext.rect(rect.x, rect.y, rect.w, rect.h); + if (iconContext.isPointInPath(loc.x, loc.y)) { + selectIcon(rect, loc); + selectedFunction = getIconFunction(rect, loc); + + if (selectedFunction === 'text') { + drawingCanvas.style.cursor = 'text'; + } + else { + drawingCanvas.style.cursor = 'crosshair'; + } + } + }); +}; + +// Key event handlers............................................ + +function backspace() { + restoreDrawingSurface(); + currentText = currentText.slice(0, -1); + eraseTextCursor(); +}; + +function enter() { + finishDrawingText(); + mousedown.y += drawingContext.measureText('W').width; + saveDrawingSurface(); + startDrawingText(); +}; + +function insert(key) { + currentText += key; + restoreDrawingSurface(); + drawCurrentText(); + drawTextCursor(); +}; + +document.onkeydown = function (e) { + if (e.keyCode === 8) { // backspace + e.preventDefault(); + backspace(); + } + else if (e.keyCode === 13) { // enter + e.preventDefault(); + enter(); + } +} + +document.onkeypress = function (e) { + var key = String.fromCharCode(e.which); + + if (editingText && e.keyCode !== 8) { + e.preventDefault(); + insert(key); + } +} + +function eraseTextCursor() { + restoreDrawingSurface(); + drawCurrentText(); +} + +function drawCurrentText() { + if (doFill) + drawingContext.fillText(currentText, mousedown.x, mousedown.y); + + drawingContext.strokeText(currentText, mousedown.x, mousedown.y); +} + +function drawTextCursor() { + var widthMetric = drawingContext.measureText(currentText), + heightMetric = drawingContext.measureText('W'), + cursorLoc = { + x: mousedown.x + widthMetric.width, + y: mousedown.y - heightMetric.width + 5 + }; + + drawingContext.beginPath(); + drawingContext.moveTo(cursorLoc.x, cursorLoc.y); + drawingContext.lineTo(cursorLoc.x, cursorLoc.y + heightMetric.width - 12); + drawingContext.stroke(); +} + +function startDrawingText() { + editingText = true; + currentText = ''; + drawTextCursor(); + showKeyboard(); +} + +function finishDrawingText() { + restoreDrawingSurface(); + drawCurrentText(); +} + +function mouseDownOrTouchStartInDrawingCanvas(loc) { + dragging = true; + + if (editingText) { + finishDrawingText(); + } + else if (editingCurve) { + if (drawingContext.isPointInPath(loc.x, loc.y)) { + draggingControlPoint = true; + } + else { + restoreDrawingSurface(); + } + editingCurve = false; + } + + if (!draggingControlPoint) { + saveDrawingSurface(); + mousedown.x = loc.x; + mousedown.y = loc.y; + + if (selectedFunction === 'path' || selectedFunction === 'pathClosed') { + drawingContext.beginPath(); + drawingContext.moveTo(loc.x, loc.y); + } + else if (selectedFunction === 'text') { + startDrawingText(); + } + else { + editingText = false; + } + + lastX = loc.x; + lastY = loc.y; + } +} + +function moveControlPoint(loc) { + controlPoint.x = loc.x; + controlPoint.y = loc.y; +} + +function mouseMoveOrTouchMoveInDrawingCanvas(loc) { + if (draggingControlPoint) { + restoreDrawingSurface(); + + moveControlPoint(loc); + + drawingContext.save(); + + drawingContext.strokeStyle = RUBBERBAND_STROKE_STYLE; + drawingContext.lineWidth = RUBBERBAND_LINE_WIDTH; + + drawCurve(); + drawControlPoint(); + + drawingContext.restore(); + } + else if (dragging) { + if (selectedFunction === 'erase') { + eraseLast(); + drawEraser(loc); + } + else if (selectedFunction === 'slinky') { + drawSlinky(loc); + } + else if (selectedFunction === 'path' || + selectedFunction === 'pathClosed') { + drawingContext.lineTo(loc.x, loc.y); + drawingContext.stroke(); + } + else { // For lines, circles, rectangles, and curves, draw rubberbands + restoreDrawingSurface(); + updateRubberbandRectangle(loc); + drawRubberband(loc); + } + + lastX = loc.x; + lastY = loc.y; + + lastRect.w = rubberbandW; + lastRect.h = rubberbandH; + } + + if (dragging || draggingControlPoint) { + if (selectedFunction === 'line' || + selectedFunction === 'rectangle' || + selectedFunction === 'circle') { + drawGuidewires(loc.x, loc.y); + } + } +}; + +function endPath(loc) { + drawingContext.lineTo(loc.x, loc.y); + drawingContext.stroke(); + + if (selectedFunction === 'pathClosed') { + drawingContext.closePath(); + + if (doFill) { + drawingContext.fill(); + } + drawingContext.stroke(); + } +} + +function mouseUpOrTouchEndInDrawingCanvas(loc) { + if (selectedFunction !== 'erase' && selectedFunction !== 'slinky') { + restoreDrawingSurface(); + } + + if (draggingControlPoint) { + moveControlPoint(loc); + finishDrawingCurve(); + draggingControlPoint = false; + } + else if (dragging) { + if (selectedFunction === 'erase') { + eraseLast(); + } + else if (selectedFunction === 'path' || + selectedFunction === 'pathClosed') { + endPath(loc); + } + else { + if (selectedFunction === 'line') finishDrawingLine(loc); + else if (selectedFunction === 'rectangle') finishDrawingRectangle(); + else if (selectedFunction === 'circle') finishDrawingCircle(loc); + else if (selectedFunction === 'curve') startEditingCurve(loc); + } + } + dragging = false; +}; + +// Control canvas event handlers................................. + +iconCanvas.onmousedown = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + loc = windowToCanvas(iconCanvas, x, y); + + e.preventDefault(); + mouseDownOrTouchStartInControlCanvas(loc); +} + +iconCanvas.addEventListener('touchstart', function (e) { + if (e.touches.length === 1) { + e.preventDefault(); + mouseDownOrTouchStartInControlCanvas( + windowToCanvas(iconCanvas, + e.touches[0].clientX, e.touches[0].clientY)); + } +}); + +// Drawing canvas event handlers................................. + +drawingCanvas.onmousedown = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY; + + e.preventDefault(); + mouseDownOrTouchStartInDrawingCanvas( + windowToCanvas(drawingCanvas, x, y)); +} + +drawingCanvas.ontouchstart = function (e) { + if (e.touches.length === 1) { + e.preventDefault(); + mouseDownOrTouchStartInDrawingCanvas( + windowToCanvas(drawingCanvas, + e.touches[0].clientX, e.touches[0].clientY)); + } +} + +drawingCanvas.ontouchmove = function (e) { + if (e.touches.length === 1) { + mouseMoveOrTouchMoveInDrawingCanvas( + windowToCanvas(drawingCanvas, + e.touches[0].clientX, e.touches[0].clientY)); + } +} + +drawingCanvas.ontouchend = function (e) { + var loc; + + if (e.changedTouches.length === 1) { + loc = windowToCanvas(drawingCanvas, e.changedTouches[0].clientX, e.changedTouches[0].clientY); + mouseUpOrTouchEndInDrawingCanvas(loc); + } +} + +drawingCanvas.onmousemove = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + loc = windowToCanvas(drawingCanvas, x, y); + + e.preventDefault(); + mouseMoveOrTouchMoveInDrawingCanvas(loc); +} + +drawingCanvas.onmouseup = function (e) { + var x = e.x || e.clientX, + y = e.y || e.clientY, + loc = windowToCanvas(drawingCanvas, x, y); + + e.preventDefault(); + mouseUpOrTouchEndInDrawingCanvas(loc); +} + +// Control event handlers........................................ + +strokeStyleSelect.onchange = function (e) { + drawingContext.strokeStyle = strokeStyleSelect.value; +}; + +fillStyleSelect.onchange = function (e) { + drawingContext.fillStyle = fillStyleSelect.value; +}; + +lineWidthSelect.onchange = function (e) { + drawingContext.lineWidth = lineWidthSelect.value; +/* +var c = drawingContext.canvas, + sw = c.width, + sh = c.height, + dw = sw * lineWidthSelect.value, + dh = sh * lineWidthSelect.value; + +drawingContext.scale(lineWidthSelect.value, lineWidthSelect.value); +drawingContext.drawImage(c, 0, 0); +*/ +}; + +eraseAllButton.onclick = function (e) { + drawingContext.clearRect(0,0, + drawingCanvas.width, + drawingCanvas.height); + drawGrid(drawingContext, GRID_LINE_COLOR, 10, 10); + saveDrawingSurface(); + rubberbandW = rubberbandH = 0; +}; + +curveInstructionsOkayButton.onclick = function (e) { + curveInstructions.style.display = 'none'; +}; + +curveInstructionsNoMoreButton.onclick = function (e) { + curveInstructions.style.display = 'none'; + showCurveInstructions = false; +}; + +snapshotButton.onclick = function (e) { + var dataUrl; + + if (snapshotButton.value === 'Take snapshot') { + dataUrl = drawingCanvas.toDataURL(); + snapshotImageElement.src = dataUrl; + snapshotImageElement.style.display = 'inline'; + snapshotInstructions.style.display = 'inline'; + drawingCanvas.style.display = 'none'; + iconCanvas.style.display = 'none'; + controls.style.display = 'none'; + snapshotButton.value = 'Back to Paint'; + } + else { + snapshotButton.value = 'Take snapshot'; + drawingCanvas.style.display = 'inline'; + iconCanvas.style.display = 'inline'; + controls.style.display = 'inline'; + snapshotImageElement.style.display = 'none'; + snapshotInstructions.style.display = 'none'; + } +}; + +function drawBackground() { + backgroundContext.canvas.width = drawingContext.canvas.width; + backgroundContext.canvas.height = drawingContext.canvas.height; + + drawGrid(backgroundContext, GRID_LINE_COLOR, 10, 10); +} + +// Initialization................................................ + +iconContext.strokeStyle = ICON_STROKE_STYLE; +iconContext.fillStyle = ICON_FILL_STYLE; + +iconContext.font = '48px Palatino'; +iconContext.textAlign = 'center'; +iconContext.textBaseline = 'middle'; + +drawingContext.font = '48px Palatino'; +drawingContext.textBaseline = 'bottom'; + +drawingContext.strokeStyle = strokeStyleSelect.value; +drawingContext.fillStyle = fillStyleSelect.value; +drawingContext.lineWidth = lineWidthSelect.value; + +drawGrid(drawingContext, GRID_LINE_COLOR, 10, 10); +selectedRect = ICON_RECTANGLES[SLINKY_ICON]; +selectedFunction = 'slinky'; + +// This event listener prevents touch devices from +// scrolling the visible viewport. + +document.body.addEventListener('touchmove', function (e) { + e.preventDefault(); +}, false); + +drawIcons(); +drawBackground(); + +/* +if (window.matchMedia) alert('found'); else alert('nope'); +var mql = window.matchMedia("(orientation:landscape)"); +if (mql.addListener) alert('found'); else alert('nope'); + +function listener(mql) { alert ('...'); } + +mql.addListener( listener ); +listener(mql); +*/ + +keyboard.appendTo('keyboard'); +keyboard.addKeyListener( function (key) { + if (key === 'Enter') enter(); + else if (key === '<') backspace(); + else insert(key); +}); diff --git a/canvas/ch11/example-11.5/icon-ipad.png b/canvas/ch11/example-11.5/icon-ipad.png new file mode 100644 index 0000000..6124c8c Binary files /dev/null and b/canvas/ch11/example-11.5/icon-ipad.png differ diff --git a/canvas/ch11/example-11.5/keyboard.js b/canvas/ch11/example-11.5/keyboard.js new file mode 100644 index 0000000..30f7558 --- /dev/null +++ b/canvas/ch11/example-11.5/keyboard.js @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {}; + +// Key Constructor.................................................................... + +COREHTML5.Key = function (text) { + this.text = text; + this.selected = false; + this.translucent = false; +} + +COREHTML5.Key.prototype = { + createPath: function (context) { + context.beginPath(); + + if (this.width > 0) context.moveTo(this.left + this.cornerRadius, this.top); + else context.moveTo(this.left - this.cornerRadius, this.top); + + context.arcTo(this.left + this.width, this.top, + this.left + this.width, this.top + this.height, + this.cornerRadius); + + context.arcTo(this.left + this.width, this.top + this.height, + this.left, this.top + this.height, + this.cornerRadius); + + context.arcTo(this.left, this.top + this.height, + this.left, this.top, + this.cornerRadius); + + if (this.width > 0) { + context.arcTo(this.left, this.top, + this.left + this.cornerRadius, this.top, + this.cornerRadius); + } + else { + context.arcTo(this.left, this.top, + this.left - this.cornerRadius, this.top, + this.cornerRadius); + } + }, + + createKeyGradient: function (context) { + var keyGradient = context.createLinearGradient( + this.left, this.top, + this.left, this.top + this.height); + if (this.selected) { + keyGradient.addColorStop(0, 'rgb(208, 208, 210)'); + keyGradient.addColorStop(1.0, 'rgb(162, 162, 166)'); + } + else if (this.translucent) { + keyGradient.addColorStop(0, 'rgba(298, 298, 300, 0.20)'); + keyGradient.addColorStop(1.0, 'rgba(255, 255, 255, 0.20)'); + } + else { + keyGradient.addColorStop(0, 'rgb(238, 238, 240)'); + keyGradient.addColorStop(1.0, 'rgb(192, 192, 196)'); + } + + return keyGradient; + }, + + setKeyProperties: function (context, keyGradient) { + context.shadowColor = 'rgba(0, 0, 0, 0.8)'; + context.shadowOffsetX = 1; + context.shadowOffsetY = 1; + context.shadowBlur = 1; + + context.lineWidth = 0.5; + + context.strokeStyle = 'rgba(0, 0, 0, 0.7)'; + context.fillStyle = keyGradient; + }, + + setTextProperties: function (context) { + context.shadowColor = undefined; + context.shadowOffsetX = 0; + + context.font = '100 ' + this.height/3 + 'px Helvetica'; + context.fillStyle = 'rgba(0,0,0,0.4)'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + }, + + draw: function (context) { + var keyGradient = this.createKeyGradient(context); + + context.save(); + + this.createPath(context); + + this.setKeyProperties(context, keyGradient); + context.stroke(); + context.fill(); + + this.setTextProperties(context); + context.fillText(this.text, this.left + this.width/2, + this.top + this.height/2); + + context.restore(); + }, + + erase: function(context) { + context.clearRect(this.left-2, this.top-2, + this.width+6, this.height+6); + }, + + redraw: function (context) { + this.erase(context); + this.draw(context); + }, + + toggleSelection: function (context) { + this.selected = !this.selected; + }, + + isPointInKey: function (context, x, y) { + this.createPath(context); + return context.isPointInPath(x, y); + }, + + select: function (key) { + this.selected = true; + }, + + deselect: function (key) { + this.selected = false; + }, +} + +// Keyboard Constructor............................................................... + +COREHTML5.Keyboard = function() { + var keyboard = this; + + this.keys = [ + [ new COREHTML5.Key('Q'), new COREHTML5.Key('W'), new COREHTML5.Key('E'), + new COREHTML5.Key('R'), new COREHTML5.Key('T'), new COREHTML5.Key('Y'), + new COREHTML5.Key('U'), new COREHTML5.Key('I'), new COREHTML5.Key('O'), + new COREHTML5.Key('P'), new COREHTML5.Key('<') ], + + [ new COREHTML5.Key('A'), new COREHTML5.Key('S'), new COREHTML5.Key('D'), + new COREHTML5.Key('F'), new COREHTML5.Key('G'), new COREHTML5.Key('H'), + new COREHTML5.Key('J'), new COREHTML5.Key('K'), new COREHTML5.Key('L'), + new COREHTML5.Key('Enter') ], + + [ new COREHTML5.Key('^'), new COREHTML5.Key('Z'), new COREHTML5.Key('X'), + new COREHTML5.Key('C'), new COREHTML5.Key('V'), new COREHTML5.Key('B'), + new COREHTML5.Key('N'), new COREHTML5.Key('M'), new COREHTML5.Key(','), + new COREHTML5.Key('.'), new COREHTML5.Key('^') ], + + [ new COREHTML5.Key(';'), new COREHTML5.Key(':'), new COREHTML5.Key(' '), + new COREHTML5.Key('?'), new COREHTML5.Key('!') ] + ]; + + this.KEYBOARD_HEIGHT = 360, + this.KEY_COLUMNS = 11, + this.KEY_ROWS = 4, + + this.createCanvas(); + this.createDOMElement(); + + this.translucent = false; + this.shifted = false; + this.keyListenerFunctions = []; + + this.context.canvas.onmousedown = function (e) { + keyboard.mouseDownOrTouchStart(keyboard.context, + keyboard.windowToCanvas(keyboard.context.canvas, e.clientX, e.clientY)); + + e.preventDefault(); // prevents inadvertent selections on desktop + }; + + this.context.canvas.ontouchstart = function (e) { + keyboard.mouseDownOrTouchStart(keyboard.context, + keyboard.windowToCanvas(keyboard.context.canvas, + e.touches[0].clientX, e.touches[0].clientY)); + + e.preventDefault(); // prevents flashing on iPad + }; + + return this; +} + +// Keyboard Constructor............................................................... + +COREHTML5.Keyboard.prototype = { + + // General functions .............................................................. + + windowToCanvas: function (canvas, x, y) { + var bbox = canvas.getBoundingClientRect(); + return { x: x - bbox.left * (canvas.width / bbox.width), + y: y - bbox.top * (canvas.height / bbox.height) + }; + }, + + createCanvas: function () { + var canvas = document.createElement('canvas'); + this.context = canvas.getContext('2d'); + }, + + createDOMElement: function () { + this.domElement = document.createElement('div'); + this.domElement.appendChild(this.context.canvas); + }, + + appendTo: function (elementName) { + var element = document.getElementById(elementName); + + element.appendChild(this.domElement); + this.domElement.style.width = element.offsetWidth + 'px'; + this.domElement.style.height = element.offsetHeight + 'px'; + this.resize(element.offsetWidth, element.offsetHeight); + this.createKeys(); + }, + + resize: function (width, height) { + this.domElement.style.width = width + 'px'; + this.domElement.style.height = height + 'px'; + + this.context.canvas.width = width; + this.context.canvas.height = height; + }, + + // Drawing Functions................................................................. + + drawRoundedRect: function (context, cornerX, cornerY, width, height, cornerRadius) { + if (width > 0) this.context.moveTo(cornerX + cornerRadius, cornerY); + else this.context.moveTo(cornerX - cornerRadius, cornerY); + + context.arcTo(cornerX + width, cornerY, + cornerX + width, cornerY + height, + cornerRadius); + + context.arcTo(cornerX + width, cornerY + height, + cornerX, cornerY + height, + cornerRadius); + + context.arcTo(cornerX, cornerY + height, + cornerX, cornerY, + cornerRadius); + + if (width > 0) { + context.arcTo(cornerX, cornerY, + cornerX + cornerRadius, cornerY, + cornerRadius); + } + else { + context.arcTo(cornerX, cornerY, + cornerX - cornerRadius, cornerY, + cornerRadius); + } + + context.stroke(); + context.fill(); + }, + + drawKeys: function () { + for (var row=0; row < this.keys.length; ++row) { + for (var col=0; col < this.keys[row].length; ++col) { + key = this.keys[row][col]; + + key.translucent = this.translucent; + key.draw(this.context); + } + } + }, + + draw: function (context) { + var originalContext, key; + + if (context) { + originalContext = this.context; + this.context = context; + } + + this.context.save(); + this.drawKeys(); + + if (context) { + this.context = originalContext; + } + + this.context.restore(); + }, + + erase: function() { + // Erase the entire canvas + this.context.clearRect(0, 0, this.context.canvas.width, + this.context.canvas.height); + }, + + // Keys.............................................................................. + + adjustKeyPosition: function (key, keyTop, keyMargin, keyWidth, spacebarPadding) { + var key = this.keys[row][col], + keyMargin = this.domElement.clientWidth / (this.KEY_COLUMNS*8), + keyWidth = + ((this.domElement.clientWidth - 2*keyMargin) / this.KEY_COLUMNS) - keyMargin, + keyLeft = keyMargin + col * keyWidth + col * keyMargin; + + if (row === 1) keyLeft += keyWidth/2; + if (row === 3) keyLeft += keyWidth/3; + + key.left = keyLeft + spacebarPadding; + key.top = keyTop; + }, + + adjustKeySize: function (key, keyMargin, keyWidth, keyHeight) { + if (key.text === 'Enter') key.width = keyWidth * 1.5; + else if (key.text === ' ') key.width = keyWidth * 7; + else key.width = keyWidth; + + key.height = keyHeight; + key.cornerRadius = 5; + }, + + createKeys: function() { + var key, + keyMargin, + keyWidth, + keyHeight, + spacebarPadding = 0; + + for (row=0; row < this.keys.length; ++row) { + for (col=0; col < this.keys[row].length; ++col) { + key = this.keys[row][col]; + keyMargin = this.domElement.clientWidth / (this.KEY_COLUMNS*8); + keyWidth = + ((this.domElement.clientWidth - 2*keyMargin) / this.KEY_COLUMNS) - keyMargin; + keyHeight = ((this.KEYBOARD_HEIGHT - 2*keyMargin) / this.KEY_ROWS) - keyMargin; + keyTop = keyMargin + row * keyHeight + row * keyMargin; + + this.adjustKeyPosition(key, keyTop, keyMargin, keyWidth, spacebarPadding); + this.adjustKeySize(key, keyMargin, keyWidth, keyHeight); + + if (this.keys[row][col].text === ' ') { + spacebarPadding = keyWidth*6; // pad from now on + } + } + } + }, + + getKeyForLocation: function (context, loc) { + var key; + + for (var row=0; row < this.keys.length; ++row) { + for (var col=0; col < this.keys[row].length; ++col) { + key = this.keys[row][col]; + + if (key.isPointInKey(context, loc.x, loc.y)) { + return key; + } + } + } + return null; + }, + + shiftKeyPressed: function (context) { + for (var row=0; row < this.keys.length; ++row) { + for (var col=0; col < this.keys[row].length; ++col) { + nextKey = this.keys[row][col]; + + if (nextKey.text === '^') { + nextKey.toggleSelection(); + nextKey.redraw(context); + this.shifted = nextKey.selected; + } + } + } + }, + + activateKey: function (key, context) { + key.select(); + setTimeout( function (e) { + key.deselect(); + key.redraw(context); + }, 200); + + key.redraw(context); + + this.fireKeyEvent(key); + }, + + // Key listeners..................................................................... + + addKeyListener: function (listenerFunction) { + this.keyListenerFunctions.push(listenerFunction); + }, + + fireKeyEvent: function (key) { + for (var i=0; i < this.keyListenerFunctions.length; ++i) { + this.keyListenerFunctions[i](this.shifted ? key.text : key.text.toLowerCase()); + } + }, + + // Event handlers.................................................................... + + mouseDownOrTouchStart: function (context, loc) { + var key = this.getKeyForLocation(context, loc); + + if (key) { + if (key.text === '^') { + this.shiftKeyPressed(context); + } + else { + if (this.shifted) this.activateKey(key, context); + else this.activateKey(key, context); + } + } + } +}; diff --git a/canvas/ch11/example-11.5/startup-iPad-landscape.png b/canvas/ch11/example-11.5/startup-iPad-landscape.png new file mode 100644 index 0000000..62ea3fd Binary files /dev/null and b/canvas/ch11/example-11.5/startup-iPad-landscape.png differ diff --git a/canvas/ch11/example-11.5/startup-iPad-portrait.png b/canvas/ch11/example-11.5/startup-iPad-portrait.png new file mode 100644 index 0000000..30a8bfb Binary files /dev/null and b/canvas/ch11/example-11.5/startup-iPad-portrait.png differ diff --git a/canvas/index.html b/canvas/index.html new file mode 100644 index 0000000..ee6e1dd --- /dev/null +++ b/canvas/index.html @@ -0,0 +1,209 @@ + + + + + + Core HTML5 Canvas Examples + + + + + +

Core HTML5 Canvas Examples

+ +

Chapter 1: Essentials

+
    +
  1. Example 1.1
  2. +
  3. Example 1.3
  4. +
  5. Example 1.4
  6. +
  7. Example 1.5
  8. +
  9. Example 1.8
  10. +
  11. Example 1.9
  12. +
  13. Example 1.11
  14. +
  15. Example 1.13
  16. +
+ +

Chapter 2: Drawing

+
    +
  1. Example 2.1
  2. +
  3. Example 2.2
  4. +
  5. Example 2.3
  6. +
  7. Example 2.4
  8. +
  9. Example 2.5
  10. +
  11. Example 2.7
  12. +
  13. Example 2.9
  14. +
  15. Example 2.10
  16. +
  17. Example 2.11
  18. +
  19. Example 2.12
  20. +
  21. Example 2.13
  22. +
  23. Example 2.14
  24. +
  25. Example 2.15
  26. +
  27. Example 2.17
  28. +
  29. Example 2.18
  30. +
  31. Example 2.19
  32. +
  33. Example 2.20
  34. +
  35. Example 2.21
  36. +
  37. Example 2.22
  38. +
  39. Example 2.23
  40. +
  41. Example 2.24
  42. +
  43. Example 2.25
  44. +
  45. Example 2.26
  46. +
  47. Example 2.28
  48. +
  49. Example 2.29
  50. +
  51. Example 2.31
  52. +
  53. Section 2.13.2.3
  54. +
  55. Example 2.32
  56. +
  57. Example 2.34
  58. +
  59. Example 2.35
  60. +
+ +

Chapter 3: Text

+
    +
  1. Example 3.1
  2. +
  3. Example 3.2
  4. +
  5. Example 3.3
  6. +
  7. Example 3.4
  8. +
  9. Example 3.5
  10. +
  11. Example 3.7
  12. +
  13. Example 3.8
  14. +
  15. Example 3.9
  16. +
  17. Example 3.12
  18. +
  19. Example 3.14
  20. +
  21. Example 3.15
  22. +
  23. Example 3.17
  24. +
  25. Example 3.18
  26. +
+ +

Chapter 4: Images and Video

+
    +
  1. Example 4.1
  2. +
  3. Example 4.2
  4. +
  5. Example 4.4
  6. +
  7. Example 4.6
  8. +
  9. Example 4.8
  10. +
  11. Example 4.9
  12. +
  13. Example 4.12
  14. +
  15. Example 4.13
  16. +
  17. Example 4.14
  18. +
  19. Example 4.15
  20. +
  21. Example 4.16
  22. +
  23. Example 4.18
  24. +
  25. Example 4.19
  26. +
  27. Example 4.20
  28. +
  29. Example 4.22
  30. +
  31. Example 4.23
  32. +
  33. Example 4.25
  34. +
+ + +

Chapter 5: Animation

+
    +
  1. Example 5.9
  2. +
  3. Example 5.11
  4. +
  5. Example 5.12
  6. +
  7. Example 5.14
  8. +
  9. Example 5.15
  10. +
  11. Example 5.17
  12. +
  13. Example 5.18
  14. +
  15. Example 5.19
  16. +
+ + +

Chapter 6: Sprites

+
    +
  1. Example 6.1
  2. +
  3. Example 6.2
  4. +
  5. Section 6.3.2
  6. +
  7. Example 6.5
  8. +
  9. Example 6.7
  10. +
  11. Example 6.9
  12. +
  13. Example 6.10
  14. +
+ + +

Chapter 7: Physics

+
    +
  1. Example 7.1
  2. +
  3. Example 7.3
  4. +
  5. Example 7.5
  6. +
  7. Example 7.8
  8. +
  9. Example 7.9
  10. +
+ + +

Chapter 8: Collision Detection

+
    +
  1. Example 8.1
  2. +
  3. Section 8.1.1
  4. +
  5. Example 8.2
  6. +
  7. Section 8.3
  8. +
  9. Section 8.4.1.6
  10. +
  11. Example 8.8
  12. +
  13. Example 8.10
  14. +
  15. Example 8.19
  16. +
  17. Example 8.20
  18. +
+ + +

Chapter 9: Game Development

+
    +
  1. The Ungame
  2. +
  3. Poker Pinball
  4. +
+ + +

Chapter 10: Custom Controls

+
    +
  1. Example 10.1
  2. +
  3. Example 10.4
  4. +
  5. Example 10.7
  6. +
  7. Example 10.10
  8. +
+ + +

Chapter 11: Mobile

+
    +
  1. Example 11.1
  2. +
  3. Example 11.3
  4. +
  5. Example 11.5
  6. +
+ + + diff --git a/canvas/shared/images/arch.png b/canvas/shared/images/arch.png new file mode 100644 index 0000000..fc7190c Binary files /dev/null and b/canvas/shared/images/arch.png differ diff --git a/canvas/shared/images/bomb.png b/canvas/shared/images/bomb.png new file mode 100644 index 0000000..9caa055 Binary files /dev/null and b/canvas/shared/images/bomb.png differ diff --git a/canvas/shared/images/bucket.png b/canvas/shared/images/bucket.png new file mode 100644 index 0000000..0e1ec43 Binary files /dev/null and b/canvas/shared/images/bucket.png differ diff --git a/canvas/shared/images/camp.png b/canvas/shared/images/camp.png new file mode 100644 index 0000000..824ba1d Binary files /dev/null and b/canvas/shared/images/camp.png differ diff --git a/canvas/shared/images/canyon.png b/canvas/shared/images/canyon.png new file mode 100644 index 0000000..c07ccc7 Binary files /dev/null and b/canvas/shared/images/canyon.png differ diff --git a/canvas/shared/images/countrypath.jpg b/canvas/shared/images/countrypath.jpg new file mode 100644 index 0000000..c2e5a76 Binary files /dev/null and b/canvas/shared/images/countrypath.jpg differ diff --git a/canvas/shared/images/curved-road.png b/canvas/shared/images/curved-road.png new file mode 100644 index 0000000..91d5eaa Binary files /dev/null and b/canvas/shared/images/curved-road.png differ diff --git a/canvas/shared/images/grand-canyon.png b/canvas/shared/images/grand-canyon.png new file mode 100644 index 0000000..c07ccc7 Binary files /dev/null and b/canvas/shared/images/grand-canyon.png differ diff --git a/canvas/shared/images/grass.png b/canvas/shared/images/grass.png new file mode 100644 index 0000000..7d6b71e Binary files /dev/null and b/canvas/shared/images/grass.png differ diff --git a/canvas/shared/images/grass2.png b/canvas/shared/images/grass2.png new file mode 100644 index 0000000..859d9a7 Binary files /dev/null and b/canvas/shared/images/grass2.png differ diff --git a/canvas/shared/images/log-crossing.png b/canvas/shared/images/log-crossing.png new file mode 100644 index 0000000..38189bf Binary files /dev/null and b/canvas/shared/images/log-crossing.png differ diff --git a/canvas/shared/images/lonelybeach.png b/canvas/shared/images/lonelybeach.png new file mode 100644 index 0000000..4b80bdf Binary files /dev/null and b/canvas/shared/images/lonelybeach.png differ diff --git a/canvas/shared/images/running-sprite-sheet.png b/canvas/shared/images/running-sprite-sheet.png new file mode 100644 index 0000000..d5f92d6 Binary files /dev/null and b/canvas/shared/images/running-sprite-sheet.png differ diff --git a/canvas/shared/images/sky.png b/canvas/shared/images/sky.png new file mode 100644 index 0000000..6e3ca25 Binary files /dev/null and b/canvas/shared/images/sky.png differ diff --git a/canvas/shared/images/smalltree.png b/canvas/shared/images/smalltree.png new file mode 100644 index 0000000..e1e5cbc Binary files /dev/null and b/canvas/shared/images/smalltree.png differ diff --git a/canvas/shared/images/tree-twotrunks.png b/canvas/shared/images/tree-twotrunks.png new file mode 100644 index 0000000..854b0c3 Binary files /dev/null and b/canvas/shared/images/tree-twotrunks.png differ diff --git a/canvas/shared/images/tree.png b/canvas/shared/images/tree.png new file mode 100644 index 0000000..859922f Binary files /dev/null and b/canvas/shared/images/tree.png differ diff --git a/canvas/shared/images/waterfall.png b/canvas/shared/images/waterfall.png new file mode 100644 index 0000000..bd4a6b5 Binary files /dev/null and b/canvas/shared/images/waterfall.png differ diff --git a/canvas/shared/js/animationTimer.js b/canvas/shared/js/animationTimer.js new file mode 100644 index 0000000..866302b --- /dev/null +++ b/canvas/shared/js/animationTimer.js @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// AnimationTimer.................................................................. +// +// An animation runs for a duration, in milliseconds. It's up to you, +// however, to start and stop the animation -- animations do not stop +// automatically. You can check to see if an animation is over with the +// isOver() method, and you can see if an animation is running with +// isRunning(). Note that animations can be over, but still running. +// +// You can also supply an optional timeWarp function that warps the percent +// completed for the animation. That warping lets you do easily incorporate +// non-linear motion, such as: ease-in, ease-out, elastic, etc. + +AnimationTimer = function (duration, timeWarp) { + this.timeWarp = timeWarp; + + if (duration !== undefined) this.duration = duration; + else this.duration = 1000; + + this.stopwatch = new Stopwatch(); +}; + +AnimationTimer.prototype = { + start: function () { + this.stopwatch.start(); + }, + + stop: function () { + this.stopwatch.stop(); + }, + + getRealElapsedTime: function () { + return this.stopwatch.getElapsedTime(); + }, + + getElapsedTime: function () { + var elapsedTime = this.stopwatch.getElapsedTime(), + percentComplete = elapsedTime / this.duration; + + if (!this.stopwatch.running) return undefined; + if (this.timeWarp == undefined) return elapsedTime; + + return elapsedTime * (this.timeWarp(percentComplete) / percentComplete); + }, + + isRunning: function() { + return this.stopwatch.running; + }, + + isOver: function () { + return this.stopwatch.getElapsedTime() > this.duration; + }, + + reset: function() { + this.stopwatch.reset(); + } +}; + +AnimationTimer.makeEaseOut = function (strength) { + return function (percentComplete) { + return 1 - Math.pow(1 - percentComplete, strength*2); + }; +}; + +AnimationTimer.makeEaseIn = function (strength) { + return function (percentComplete) { + return Math.pow(percentComplete, strength*2); + }; +}; + +AnimationTimer.makeEaseInOut = function () { + return function (percentComplete) { + return percentComplete - Math.sin(percentComplete*2*Math.PI) / (2*Math.PI); + }; +}; + +AnimationTimer.makeElastic = function (passes) { + passes = passes || 3; + return function (percentComplete) { + return ((1-Math.cos(percentComplete * Math.PI * passes)) * + (1 - percentComplete)) + percentComplete; + }; +}; + +AnimationTimer.makeBounce = function (bounces) { + var fn = AnimationTimer.makeElastic(bounces); + return function (percentComplete) { + percentComplete = fn(percentComplete); + return percentComplete <= 1 ? percentComplete : 2-percentComplete; + }; +}; + +AnimationTimer.makeLinear = function () { + return function (percentComplete) { + return percentComplete; + }; +}; diff --git a/canvas/shared/js/gameEngine.js b/canvas/shared/js/gameEngine.js new file mode 100644 index 0000000..c40d5fc --- /dev/null +++ b/canvas/shared/js/gameEngine.js @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var getTimeNow = function () { + return +new Date(); +}; + +// Game....................................................................... + +// This game engine implements a game loop that draws sprites. See sprites.js. +// +// The game engine also has support for: +// +// Time-based motion (game.pixelsPerFrame()) +// Pause (game.togglePaused()) +// High Scores (game.setHighScore(), game.getHighScores(), game.clearHighScores()) +// Sound (game.canPlaySound(), game.playSound()) +// Accessing frame rate (game.fps) +// Accessing game time (game.gameTime) +// Key processing (game.addKeyListener()) +// +// The game engine's animate() method invokes the following methods, +// in the order listed: +// +// game.startAnimate() +// game.paintUnderSprites() +// game.paintOverSprites() +// game.endAnimate() +// +// Those four methods are implemented by the game engine to do nothing. +// You override those do-nothing implementations to make the game come alive. + +var Game = function (gameName, canvasId) { + var canvas = document.getElementById(canvasId), + self = this; // Used by key event handlers below + + // General + + this.context = canvas.getContext('2d'); + this.gameName = gameName; + this.sprites = []; + this.keyListeners = []; + + // High scores + + this.HIGH_SCORES_SUFFIX = '_highscores'; + + // Image loading + + this.imageLoadingProgressCallback; + this.images = {}; + this.imageUrls = []; + this.imagesLoaded = 0; + this.imagesFailedToLoad = 0; + this.imagesIndex = 0; + + // Time + + this.startTime = 0; + this.lastTime = 0; + this.gameTime = 0; + this.fps = 0; + this.STARTING_FPS = 60; + + this.paused = false; + this.startedPauseAt = 0; + this.PAUSE_TIMEOUT = 100; + + // Sound + + this.soundOn = true; + this.soundChannels = []; + this.audio = new Audio(); + this.NUM_SOUND_CHANNELS = 10; + + for (var i=0; i < this.NUM_SOUND_CHANNELS; ++i) { + var audio = new Audio(); + this.soundChannels.push(audio); + } + + // The this object in the following event handlers is the + // DOM window, which is why the functions call + // self.keyPressed() instead of this.keyPressed(e). + + window.onkeypress = function (e) { self.keyPressed(e) }; + window.onkeydown = function (e) { self.keyPressed(e); }; + + return this; +}; + +// Game methods............................................................... + +Game.prototype = { + // Given a URL, return the associated image + + getImage: function (imageUrl) { + return this.images[imageUrl]; + }, + + // This method is called by loadImage() when + // an image loads successfully. + + imageLoadedCallback: function (e) { + this.imagesLoaded++; + }, + + // This method is called by loadImage() when + // an image does not load successfully. + + imageLoadErrorCallback: function (e) { + this.imagesFailedToLoad++; + }, + + // Loads a particular image + + loadImage: function (imageUrl) { + var image = new Image(), + self = this; // load and error event handlers called by DOMWindow + + image.src = imageUrl; + + image.addEventListener('load', + function (e) { + self.imageLoadedCallback(e); + }); + + image.addEventListener('error', + function (e) { + self.imageLoadErrorCallback(e); + }); + + this.images[imageUrl] = image; + }, + + // You call this method repeatedly to load images that have been + // queued (by calling queueImage()). This method returns the + // percent of the games images that have been processed. When + // the method returns 100, all images are loaded, and you can + // quit calling this method. + + loadImages: function () { + + // If there are images left to load + + if (this.imagesIndex < this.imageUrls.length) { + this.loadImage(this.imageUrls[this.imagesIndex]); + this.imagesIndex++; + } + + // Return the percent complete + + return (this.imagesLoaded + this.imagesFailedToLoad) / + this.imageUrls.length * 100; + }, + + // Call this method to add an image to the queue. The image + // will be loaded by loadImages(). + + queueImage: function (imageUrl) { + this.imageUrls.push(imageUrl); + }, + + // Game loop.................................................................. + + // Starts the animation by invoking window.requestNextAnimationFrame(). + // + // window.requestNextAnimationFrame() is a polyfill method implemented in + // requestNextAnimationFrame.js. You pass requestNextAnimationFrame() a + // reference to a function that the browser calls when it's time to draw + // the next animation frame. + // + // When it's time to draw the next animation frame, the browser invokes + // the function that you pass to requestNextAnimationFrame(). Because that + // function is invoked by the browser (the window object, to be more exact), + // the this variable in that function will be the window object. We want + // the this variable to be the game instead, so we use JavaScript's built-in + // call() function to call the function, with the game specified as the + // this variable. + + start: function () { + var self = this; // The this variable is the game + this.startTime = getTimeNow(); // Record game's startTime (used for pausing) + + window.requestNextAnimationFrame( + function (time) { + // The this variable in this function is the window, not the game, + // which is why we do not simply do this: animate.call(time). + + self.animate.call(self, time); // self is the game + }); + }, + + // Drives the game's animation. This method is called by the browser when + // it's time for the next animation frame. + // + // If the game is paused, animate() reschedules another call to animate() + // in PAUSE_TIMEOUT (100) ms. + // + // If the game is not paused, animate() paints the next animation frame and + // reschedules another call to animate() when it's time to draw the + // next animation frame. + // + // The implementations of this.startAnimate(), this.paintUnderSprites(), + // this.paintOverSprites(), and this.endAnimate() do nothing. You override + // those methods to create the animation frame. + + animate: function (time) { + var self = this; // window.requestNextAnimationFrame() called by DOMWindow + + if (this.paused) { + // In PAUSE_TIMEOUT (100) ms, call this method again to see if the game + // is still paused. There's no need to check more frequently. + + setTimeout( function () { + window.requestNextAnimationFrame( + function (time) { + self.animate.call(self, time); + }); + }, this.PAUSE_TIMEOUT); + } + else { // Game is not paused + this.tick(time); // Update fps, game time + this.clearScreen(); // Clear the screen in preparation for next frame + + this.startAnimate(time); // Override as you wish + this.paintUnderSprites(); // Override as you wish + + this.updateSprites(time); // Invoke sprite behaviors + this.paintSprites(time); // Paint sprites in the canvas + + this.paintOverSprites(); // Override as you wish + this.endAnimate(); // Override as you wish + + this.lastTime = time; + + // Call this method again when it's time for the next animation frame + + window.requestNextAnimationFrame( + function (time) { + self.animate.call(self, time); // The this variable refers to the window + }); + } + }, + + // Update the frame rate, game time, and the last time the application + // drew an animation frame. + + tick: function (time) { + this.updateFrameRate(time); + this.gameTime = (getTimeNow()) - this.startTime; + }, + + // Update the frame rate, based on the amount of time it took + // for the last animation frame only. + + updateFrameRate: function (time) { + if (this.lastTime === 0) this.fps = this.STARTING_FPS; + else this.fps = 1000 / (time - this.lastTime); + }, + + // Clear the entire canvas. + + clearScreen: function () { + this.context.clearRect(0, 0, + this.context.canvas.width, this.context.canvas.height); + }, + + // Update all sprites. The sprite update() method invokes all + // of a sprite's behaviors. + + updateSprites: function (time) { + for(var i=0; i < this.sprites.length; ++i) { + var sprite = this.sprites[i]; + sprite.update(this.context, time); + }; + }, + + // Paint all visible sprites. + + paintSprites: function (time) { + for(var i=0; i < this.sprites.length; ++i) { + var sprite = this.sprites[i]; + if (sprite.visible) + sprite.paint(this.context); + }; + }, + + // Toggle the paused state of the game. If, after + // toggling, the paused state is unpaused, the + // application subtracts the time spent during + // the pause from the game's start time. That + // means the game picks up where it left off, + // without a potentially large jump in time. + + togglePaused: function () { + var now = getTimeNow(); + + this.paused = !this.paused; + + if (this.paused) { + this.startedPauseAt = now; + } + else { // not paused + // Adjust start time, so game starts where it left off when + // the user paused it. + + this.startTime = this.startTime + now - this.startedPauseAt; + this.lastTime = now; + } + }, + + // Given a velocity of some object, calculate the number of pixels to + // move that object for the current frame. + + pixelsPerFrame: function (time, velocity) { + // Sprites move a certain amount of pixels per frame (pixels/frame). + // This methods returns the amount of pixels a sprite should move + // for a given frame. Sprite velocity is measured in pixels / second, + // so: (pixels/second) * (second/frame) = pixels/frame: + + return velocity / this.fps; // pixels / frame + }, + + // High scores................................................................ + + // Returns an array of high scores from local storage. + + getHighScores: function () { + var key = this.gameName + this.HIGH_SCORES_SUFFIX, + highScoresString = localStorage[key]; + + if (highScoresString == undefined) { + localStorage[key] = JSON.stringify([]); + } + return JSON.parse(localStorage[key]); + }, + + // Sets the high score in local storage. + + setHighScore: function (highScore) { + var key = this.gameName + this.HIGH_SCORES_SUFFIX, + highScoresString = localStorage[key]; + + highScores.unshift(highScore); + localStorage[key] = JSON.stringify(highScores); + }, + + // Removes the high scores from local storage. + + clearHighScores: function () { + localStorage[this.gameName + this.HIGH_SCORES_SUFFIX] = JSON.stringify([]); + }, + + // Key listeners.............................................................. + + // Add a (key, listener) pair to the keyListeners array. + + addKeyListener: function (keyAndListener) { + this.keyListeners.push(keyAndListener); + }, + + // Given a key, return the associated listener. + + findKeyListener: function (key) { + var listener = undefined; + + for(var i=0; i < this.keyListeners.length; ++i) { + var keyAndListener = this.keyListeners[i], + currentKey = keyAndListener.key; + if (currentKey === key) { + listener = keyAndListener.listener; + } + }; + return listener; + }, + + // This method is the call back for key down and key press + // events. + + keyPressed: function (e) { + var listener = undefined, + key = undefined; + + switch (e.keyCode) { + // Add more keys as needed + + case 32: key = 'space'; break; + case 68: key = 'd'; break; + case 75: key = 'k'; break; + case 83: key = 's'; break; + case 80: key = 'p'; break; + case 37: key = 'left arrow'; break; + case 39: key = 'right arrow'; break; + case 38: key = 'up arrow'; break; + case 40: key = 'down arrow'; break; + } + + listener = this.findKeyListener(key); + if (listener) { // listener is a function + listener(); // invoke the listener function + } + }, + + // Sound...................................................................... + + // Returns true if the browser can play sounds in the ogg file format. + + canPlayOggVorbis: function () { + return "" != this.audio.canPlayType('audio/ogg; codecs="vorbis"'); + }, + + // Returns true if the browser can play sounds in the mp3 file format. + + canPlayMp3: function () { + return "" != this.audio.canPlayType('audio/mpeg'); + }, + + // Returns the first available sound channel from the array of sound channels. + + getAvailableSoundChannel: function () { + var audio; + + for (var i=0; i < this.NUM_SOUND_CHANNELS; ++i) { + audio = this.soundChannels[i]; + + if (audio.played.length === 0 || audio.ended) { + return audio; + } + } + return undefined; // all channels in use + }, + + // Given an identifier, play the associated sound. + + playSound: function (id) { + var channel = this.getAvailableSoundChannel(), + element = document.getElementById(id); + + if (channel && element) { + channel.src = element.src === '' ? element.currentSrc : element.src; + channel.load(); + channel.play(); + } + }, + + + // Sprites.................................................................... + + // Add a sprite to the game. The game engine will update the sprite and + // paint it (if it's visible) in the animate() method. + + addSprite: function (sprite) { + this.sprites.push(sprite); + }, + + // It's probably a good idea not to access sprites directly, because + // it's better to write generalized code that deals with all + // sprites, so this method should be used sparingly. + + getSprite: function (name) { + for(i in this.sprites) { + if (this.sprites[i].name === name) + return this.sprites[i]; + } + return null; + }, + + // Override the following methods as desired: + + startAnimate: function (time) { }, // These methods are called by + paintUnderSprites: function () { }, // animate() in the order they + paintOverSprites: function () { }, // are listed. Override them + endAnimate: function () { } // as you wish. +}; diff --git a/canvas/shared/js/pan.js b/canvas/shared/js/pan.js new file mode 100644 index 0000000..d5055f0 --- /dev/null +++ b/canvas/shared/js/pan.js @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || { }; + +// Constructor........................................................ + +COREHTML5.Pan = function(imageCanvas, image, + viewportPercent, panCanvasAlpha) { + var self = this; + + // Store arguments in member variables + + this.imageCanvas = imageCanvas; + this.image = image; + this.viewportPercent = viewportPercent || 10; + this.panCanvasAlpha = panCanvasAlpha || 0.5; + + // Get a reference to the image canvas's context, + // and create the pan canvas and the DOM element. + // Put the pan canvas in the DOM element. + + this.imageContext = imageCanvas.getContext('2d'); + this.panCanvas = document.createElement('canvas'); + this.panContext = this.panCanvas.getContext('2d'); + + this.domElement = document.createElement('div'); + this.domElement.appendChild(this.panCanvas); + + // If the image is not loaded, initialize when the image loads; + // otherwise, initialize now. + + if (image.width == 0 || image.height == 0) { // image not loaded + image.onload = function(e) { + self.initialize(); + }; + } + else { + this.initialize(); + } + return this; +}; + +// Prototype.......................................................... + +COREHTML5.Pan.prototype = { + initialize: function () { + var width = this.image.width * (this.viewportPercent/100), + height = this.image.height * (this.viewportPercent/100); + + this.addEventHandlers(); + this.setupViewport (width, height); + this.setupDOMElement(width, height); + this.setupPanCanvas (width, height); + this.draw(); + }, + + setupPanCanvas: function (w, h) { + this.panCanvas.width = w; + this.panCanvas.height = h; + }, + + setupDOMElement: function (w, h) { + this.domElement.style.width = w + 'px'; + this.domElement.style.height = h + 'px'; + this.domElement.className = 'pan'; + }, + + setupViewport: function (w, h) { + this.viewportLocation = { x: 0, y: 0 }; + this.viewportSize = { width: 50, height: 50 }; + this.viewportLastLocation = { x: 0, y: 0 }; + + this.viewportSize.width = this.imageCanvas.width * + this.viewportPercent/100; + + this.viewportSize.height = this.imageCanvas.height * + this.viewportPercent/100; + }, + + moveViewport: function(mouse, offset) { + this.viewportLocation.x = mouse.x - offset.x; + this.viewportLocation.y = mouse.y - offset.y; + + var delta = { + x: this.viewportLastLocation.x - this.viewportLocation.x, + y: this.viewportLastLocation.y - this.viewportLocation.y + }; + + this.imageContext.translate( + delta.x * (this.image.width / this.panCanvas.width), + delta.y * (this.image.height / this.panCanvas.height)); + + this.viewportLastLocation.x = this.viewportLocation.x; + this.viewportLastLocation.y = this.viewportLocation.y; + }, + + isPointInViewport: function (x, y) { + this.panContext.beginPath(); + this.panContext.rect(this.viewportLocation.x, + this.viewportLocation.y, + this.viewportSize.width, + this.viewportSize.height); + + return this.panContext.isPointInPath(x, y); + }, + + addEventHandlers: function() { + var pan = this; + + pan.domElement.onmousedown = function(e) { + var mouse = pan.windowToCanvas(e.clientX, e.clientY), + offset = null; + + e.preventDefault(); + + if (pan.isPointInViewport(mouse.x, mouse.y)) { + offset = { x: mouse.x - pan.viewportLocation.x, + y: mouse.y - pan.viewportLocation.y }; + + pan.panCanvas.onmousemove = function(e) { + pan.erase(); + + pan.moveViewport( + pan.windowToCanvas(e.clientX, e.clientY), offset); + + pan.draw(); + }; + + pan.panCanvas.onmouseup = function(e) { + pan.panCanvas.onmousemove = undefined; + pan.panCanvas.onmouseup = undefined; + }; + } + }; + }, + + erase: function() { + this.panContext.clearRect(0,0, + this.panContext.canvas.width, + this.panContext.canvas.height); + }, + + drawPanCanvas: function(alpha) { + this.panContext.save(); + this.panContext.globalAlpha = alpha; + this.panContext.drawImage(this.image, + 0, 0, + this.image.width, + this.image.height, + 0, 0, + this.panCanvas.width, + this.panCanvas.height); + this.panContext.restore(); + }, + + drawImageCanvas: function() { + this.imageContext.drawImage(this.image, + 0, 0, + this.image.width, + this.image.height); + }, + + drawViewport: function () { + this.panContext.shadowColor = 'rgba(0,0,0,0.4)'; + this.panContext.shadowOffsetX = 2; + this.panContext.shadowOffsetY = 2; + this.panContext.shadowBlur = 3; + + this.panContext.lineWidth = 3; + this.panContext.strokeStyle = 'white'; + this.panContext.strokeRect(this.viewportLocation.x, + this.viewportLocation.y, + this.viewportSize.width, + this.viewportSize.height); + }, + + clipToViewport: function() { + this.panContext.beginPath(); + this.panContext.rect(this.viewportLocation.x, + this.viewportLocation.y, + this.viewportSize.width, + this.viewportSize.height); + this.panContext.clip(); + }, + + draw: function() { + this.drawImageCanvas(); + this.drawPanCanvas(this.panCanvasAlpha); + + this.panContext.save(); + this.clipToViewport(); + this.drawPanCanvas(1.0); + this.panContext.restore(); + + this.drawViewport(); + }, + + windowToCanvas: function(x, y) { + var bbox = this.panCanvas.getBoundingClientRect(); + + return { + x: x - bbox.left * (this.panCanvas.width / bbox.width), + y: y - bbox.top * (this.panCanvas.height / bbox.height) + }; + }, +}; diff --git a/canvas/shared/js/polygon.js b/canvas/shared/js/polygon.js new file mode 100644 index 0000000..86d3ac6 --- /dev/null +++ b/canvas/shared/js/polygon.js @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var Point = function (x, y) { + this.x = x; + this.y = y; +}; + +var Polygon = function (centerX, centerY, radius, sides, startAngle, strokeStyle, fillStyle, filled) { + this.x = centerX; + this.y = centerY; + this.radius = radius; + this.sides = sides; + this.startAngle = startAngle; + this.strokeStyle = strokeStyle; + this.fillStyle = fillStyle; + this.filled = filled; +}; + +Polygon.prototype = { + getPoints: function () { + var points = [], + angle = this.startAngle || 0; + + for (var i=0; i < this.sides; ++i) { + points.push(new Point(this.x + this.radius * Math.sin(angle), + this.y - this.radius * Math.cos(angle))); + angle += 2*Math.PI/this.sides; + } + return points; + }, + + createPath: function (context) { + var points = this.getPoints(); + + context.beginPath(); + + context.moveTo(points[0].x, points[0].y); + + for (var i=1; i < this.sides; ++i) { + context.lineTo(points[i].x, points[i].y); + } + + context.closePath(); + }, + + stroke: function (context) { + context.save(); + this.createPath(context); + context.strokeStyle = this.strokeStyle; + context.stroke(); + context.restore(); + }, + + fill: function (context) { + context.save(); + this.createPath(context); + context.fillStyle = this.fillStyle; + context.fill(); + context.restore(); + }, + + move: function (x, y) { + this.x = x; + this.y = y; + }, +}; diff --git a/canvas/shared/js/progressbar.js b/canvas/shared/js/progressbar.js new file mode 100644 index 0000000..2b88dae --- /dev/null +++ b/canvas/shared/js/progressbar.js @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {} + +// Constructor.................................................... + +COREHTML5.Progressbar = function(strokeStyle, fillStyle, + horizontalSizePercent, + verticalSizePercent) { + this.trough = new COREHTML5.RoundedRectangle(strokeStyle, + fillStyle, + horizontalSizePercent, + verticalSizePercent); + this.SHADOW_COLOR = 'rgba(255,255,255,0.5)'; + this.SHADOW_BLUR = 3; + this.SHADOW_OFFSET_X = 2; + this.SHADOW_OFFSET_Y = 2; + + this.percentComplete = 0; + this.createCanvases(); + this.createDOMElement(); + + return this; +} + +// Prototype...................................................... + + COREHTML5.Progressbar.prototype = { + createDOMElement: function () { + this.domElement = document.createElement('div'); + this.domElement.appendChild(this.context.canvas); + }, + + createCanvases: function () { + this.context = document.createElement('canvas'). + getContext('2d'); + + this.offscreen = document.createElement('canvas'). + getContext('2d'); + }, + + appendTo: function (element) { + element.appendChild(this.domElement); + + this.domElement.style.width = + element.offsetWidth + 'px'; + + this.domElement.style.height = + element.offsetHeight + 'px'; + + this.resize(); // obliterates everything in the canvases + + this.trough.resize(element.offsetWidth, + element.offsetHeight); + this.trough.draw(this.offscreen); + }, + + setCanvasSize: function () { + var domElementParent = this.domElement.parentNode; + + this.context.canvas.width = domElementParent.offsetWidth; + this.context.canvas.height = domElementParent.offsetHeight; + }, + + resize: function () { + var domElementParent = this.domElement.parentNode, + w = domElementParent.offsetWidth, + h = domElementParent.offsetHeight; + + this.setCanvasSize(); + + this.context.canvas.width = w; + this.context.canvas.height = h; + + this.offscreen.canvas.width = w; + this.offscreen.canvas.height = h; + }, + + draw: function (percentComplete) { + if (percentComplete > 0) { + // Copy the appropriate region of the foreground canvas + // to the same region of the onscreen canvas + + this.context.drawImage( + this.offscreen.canvas, 0, 0, + this.offscreen.canvas.width*(percentComplete/100), + this.offscreen.canvas.height, + 0, 0, + this.offscreen.canvas.width*(percentComplete/100), + this.offscreen.canvas.height); + } + }, + + erase: function() { + this.context.clearRect(0, 0, + this.context.canvas.width, + this.context.canvas.height); + }, +}; diff --git a/canvas/shared/js/requestNextAnimationFrame.js b/canvas/shared/js/requestNextAnimationFrame.js new file mode 100644 index 0000000..e07bfb3 --- /dev/null +++ b/canvas/shared/js/requestNextAnimationFrame.js @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +window.requestNextAnimationFrame = + (function () { + var originalWebkitRequestAnimationFrame = undefined, + wrapper = undefined, + callback = undefined, + geckoVersion = 0, + userAgent = navigator.userAgent, + index = 0, + self = this; + + // Workaround for Chrome 10 bug where Chrome + // does not pass the time to the animation function + + if (window.webkitRequestAnimationFrame) { + // Define the wrapper + + wrapper = function (time) { + if (time === undefined) { + time = +new Date(); + } + self.callback(time); + }; + + // Make the switch + + originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame; + + window.webkitRequestAnimationFrame = function (callback, element) { + self.callback = callback; + + // Browser calls the wrapper and wrapper calls the callback + + originalWebkitRequestAnimationFrame(wrapper, element); + } + } + + // Workaround for Gecko 2.0, which has a bug in + // mozRequestAnimationFrame() that restricts animations + // to 30-40 fps. + + if (window.mozRequestAnimationFrame) { + // Check the Gecko version. Gecko is used by browsers + // other than Firefox. Gecko 2.0 corresponds to + // Firefox 4.0. + + index = userAgent.indexOf('rv:'); + + if (userAgent.indexOf('Gecko') != -1) { + geckoVersion = userAgent.substr(index + 3, 3); + + if (geckoVersion === '2.0') { + // Forces the return statement to fall through + // to the setTimeout() function. + + window.mozRequestAnimationFrame = undefined; + } + } + } + + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + + function (callback, element) { + var start, + finish; + + window.setTimeout( function () { + start = +new Date(); + callback(start); + finish = +new Date(); + + self.timeout = 1000 / 60 - (finish - start); + + }, self.timeout); + }; + } + ) +(); diff --git a/canvas/shared/js/roundedRectangle.js b/canvas/shared/js/roundedRectangle.js new file mode 100644 index 0000000..226940c --- /dev/null +++ b/canvas/shared/js/roundedRectangle.js @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {} + +// Constructor........................................................ + +COREHTML5.RoundedRectangle = function(strokeStyle, fillStyle, + horizontalSizePercent, + verticalSizePercent) { + this.strokeStyle = strokeStyle ? strokeStyle : 'gray'; + this.fillStyle = fillStyle ? fillStyle : 'skyblue'; + + horizontalSizePercent = horizontalSizePercent || 100; + verticalSizePercent = verticalSizePercent || 100; + + this.SHADOW_COLOR = 'rgba(100, 100, 100, 0.8)'; + this.SHADOW_OFFSET_X = 3; + this.SHADOW_OFFSET_Y = 3; + this.SHADOW_BLUR = 3; + + this.setSizePercents(horizontalSizePercent, + verticalSizePercent); + + this.createCanvas(); + this.createDOMElement(); + + return this; +} + +// Prototype.......................................................... + +COREHTML5.RoundedRectangle.prototype = { + + // General functions .............................................. + + createCanvas: function () { + var canvas = document.createElement('canvas'); + this.context = canvas.getContext('2d'); + return canvas; + }, + + createDOMElement: function () { + this.domElement = document.createElement('div'); + this.domElement.appendChild(this.context.canvas); + }, + + appendTo: function (element) { + element.appendChild(this.domElement); + this.domElement.style.width = element.offsetWidth + 'px'; + this.domElement.style.height = element.offsetHeight + 'px'; + this.resize(element.offsetWidth, element.offsetHeight); + }, + + resize: function (width, height) { + this.HORIZONTAL_MARGIN = (width - width * + this.horizontalSizePercent)/2; + + this.VERTICAL_MARGIN = (height - height * + this.verticalSizePercent)/2; + + this.cornerRadius = (this.context.canvas.height/2 - + 2*this.VERTICAL_MARGIN)/2; + + this.top = this.VERTICAL_MARGIN; + this.left = this.HORIZONTAL_MARGIN; + this.right = this.left + width - 2*this.HORIZONTAL_MARGIN; + this.bottom = this.top + height - 2*this.VERTICAL_MARGIN; + + this.context.canvas.width = width; + this.context.canvas.height = height; + }, + + setSizePercents: function (h, v) { + // horizontalSizePercent and verticalSizePercent + // represent the size of the rounded rectangle in terms + // of horizontal and vertical percents of the rectangle's + // enclosing DOM element. + + this.horizontalSizePercent = h > 1 ? h/100 : h; + this.verticalSizePercent = v > 1 ? v/100 : v; + }, + + // Drawing Functions............................................... + + fill: function () { + var radius = (this.bottom - this.top) / 2; + + this.context.save(); + this.context.shadowColor = this.SHADOW_COLOR; + this.context.shadowOffsetX = this.SHADOW_OFFSET_X; + this.context.shadowOffsetY = this.SHADOW_OFFSET_Y; + this.context.shadowBlur = 6; + + this.context.beginPath(); + + this.context.moveTo(this.left + radius, this.top); + + this.context.arcTo(this.right, this.top, + this.right, this.bottom, radius); + + this.context.arcTo(this.right, this.bottom, + this.left, this.bottom, radius); + + this.context.arcTo(this.left, this.bottom, + this.left, this.top, radius); + + this.context.arcTo(this.left, this.top, + this.right, this.top, radius); + + this.context.closePath(); + + this.context.fillStyle = this.fillStyle; + this.context.fill(); + this.context.shadowColor = undefined; + }, + + overlayGradient: function () { + var gradient = + this.context.createLinearGradient(this.left, this.top, + this.left, this.bottom); + + gradient.addColorStop(0, 'rgba(255,255,255,0.4)'); + gradient.addColorStop(0.2, 'rgba(255,255,255,0.6)'); + gradient.addColorStop(0.25, 'rgba(255,255,255,0.7)'); + gradient.addColorStop(0.3, 'rgba(255,255,255,0.9)'); + gradient.addColorStop(0.40, 'rgba(255,255,255,0.7)'); + gradient.addColorStop(0.45, 'rgba(255,255,255,0.6)'); + gradient.addColorStop(0.60, 'rgba(255,255,255,0.4)'); + gradient.addColorStop(1, 'rgba(255,255,255,0.1)'); + + this.context.fillStyle = gradient; + this.context.fill(); + + this.context.lineWidth = 0.4; + this.context.strokeStyle = this.strokeStyle; + this.context.stroke(); + + this.context.restore(); + }, + + draw: function (context) { + var originalContext; + + if (context) { + originalContext = this.context; + this.context = context; + } + + this.fill(); + this.overlayGradient(); + + + if (context) { + this.context = originalContext; + } + }, + + erase: function() { + // Erase the entire canvas + + this.context.clearRect(0, 0, this.context.canvas.width, + this.context.canvas.height); + } +}; diff --git a/canvas/shared/js/shapes.js b/canvas/shared/js/shapes.js new file mode 100644 index 0000000..0298941 --- /dev/null +++ b/canvas/shared/js/shapes.js @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var Point = function (x, y) { + this.x = x; + this.y = y; +}; + +var Shape = function () { + this.x = undefined; + this.y = undefined; + this.strokeStyle = 'rgba(255, 253, 208, 0.9)'; + this.fillStyle = 'rgba(147, 197, 114, 0.8)'; +}; + +Shape.prototype = { + collidesWith: function (shape) { + var axes = this.getAxes().concat(shape.getAxes()); + return !this.separationOnAxes(axes, shape); + }, + + separationOnAxes: function (axes, shape) { + for (var i=0; i < axes.length; ++i) { + axis = axes[i]; + projection1 = shape.project(axis); + projection2 = this.project(axis); + + if (! projection1.overlaps(projection2)) { + return true; // don't have to test remaining axes + } + } + return false; + }, + + move: function (dx, dy) { + throw 'move(dx, dy) not implemented'; + }, + + createPath: function (context) { + throw 'createPath(context) not implemented'; + }, + + getAxes: function () { + throw 'getAxes() not implemented'; + }, + + project: function (axis) { + throw 'project(axis) not implemented'; + }, + + fill: function (context) { + context.save(); + context.fillStyle = this.fillStyle; + this.createPath(context); + context.fill(); + context.restore(); + }, + + stroke: function (context) { + context.save(); + context.strokeStyle = this.strokeStyle; + this.createPath(context); + context.stroke(); + context.restore(); + }, + + isPointInPath: function (context, x, y) { + this.createPath(context); + return context.isPointInPath(x, y); + }, +}; + +var Projection = function (min, max) { + this.min = min; + this.max = max; +}; + +Projection.prototype = { + overlaps: function (projection) { + return this.max > projection.min && projection.max > this.min; + } +}; + +var Vector = function(x, y) { + this.x = x; + this.y = y; +}; + +Vector.prototype = { + getMagnitude: function () { + return Math.sqrt(Math.pow(this.x, 2) + + Math.pow(this.y, 2)); + }, + + add: function (vector) { + var v = new Vector(); + v.x = this.x + vector.x; + v.y = this.y + vector.y; + return v; + }, + + subtract: function (vector) { + var v = new Vector(); + v.x = this.x - vector.x; + v.y = this.y - vector.y; + return v; + }, + + dotProduct: function (vector) { + return this.x * vector.x + + this.y * vector.y; + }, + + edge: function (vector) { + return this.subtract(vector); + }, + + perpendicular: function () { + var v = new Vector(); + v.x = this.y; + v.y = 0-this.x; + return v; + }, + + normalize: function () { + var v = new Vector(), + m = this.getMagnitude(); + v.x = this.x / m; + v.y = this.y / m; + return v; + }, + + normal: function () { + var p = this.perpendicular(); + return p.normalize(); + } +}; + +var Polygon = function () { + this.points = []; + this.strokeStyle = 'blue'; + this.fillStyle = 'white'; +}; + +Polygon.prototype = new Shape(); + +Polygon.prototype.getAxes = function () { + var v1 = new Vector(), + v2 = new Vector(), + axes = []; + + for (var i=0; i < this.points.length-1; i++) { + v1.x = this.points[i].x; + v1.y = this.points[i].y; + + v2.x = this.points[i+1].x; + v2.y = this.points[i+1].y; + + axes.push(v1.edge(v2).normal()); + } + + v1.x = this.points[this.points.length-1].x; + v1.y = this.points[this.points.length-1].y; + + v2.x = this.points[0].x; + v2.y = this.points[0].y; + + axes.push(v1.edge(v2).normal()); + + return axes; +}; + +Polygon.prototype.project = function (axis) { + var scalars = [], + v = new Vector(); + + this.points.forEach( function (point) { + v.x = point.x; + v.y = point.y; + scalars.push(v.dotProduct(axis)); + }); + + return new Projection(Math.min.apply(Math, scalars), + Math.max.apply(Math, scalars)); +}; + +Polygon.prototype.addPoint = function (x, y) { + this.points.push(new Point(x,y)); +}; + +Polygon.prototype.createPath = function (context) { + if (this.points.length === 0) + return; + + context.beginPath(); + context.moveTo(this.points[0].x, + this.points[0].y); + + for (var i=0; i < this.points.length; ++i) { + context.lineTo(this.points[i].x, + this.points[i].y); + } + + context.closePath(); +}; + +Polygon.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +Polygon.prototype.move = function (dx, dy) { + for (var i=0, point; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } +}; + +var ImageShape = function(imageSource, x, y, w, h) { + var self = this; + + this.image = new Image(); + this.imageLoaded = false; + this.points = [ new Point(x,y) ]; + this.x = x; + this.y = y; + + this.image.src = imageSource; + + this.image.addEventListener('load', function (e) { + self.setPolygonPoints(); + self.imageLoaded = true; + }, false); +} + +ImageShape.prototype = new Polygon(); + +ImageShape.prototype.fill = function (context) { }; + +ImageShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x + this.image.width, this.y)); + this.points.push(new Point(this.x + this.image.width, this.y + this.image.height)); + this.points.push(new Point(this.x, this.y + this.image.height)); +}; + +ImageShape.prototype.drawImage = function (context) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); +}; + +ImageShape.prototype.stroke = function (context) { + var self = this; + + if (this.imageLoaded) { + context.drawImage(this.image, this.points[0].x, this.points[0].y); + } + else { + this.image.addEventListener('load', function (e) { + self.drawImage(context); + }, false); + } +}; + +var SpriteShape = function (sprite, x, y) { + this.sprite = sprite; + this.x = x; + this.y = y; + sprite.left = x; + sprite.top = y; + this.setPolygonPoints(); +}; + +SpriteShape.prototype = new Polygon(); + +SpriteShape.prototype.move = function (dx, dy) { + var point, x; + for(var i=0; i < this.points.length; ++i) { + point = this.points[i]; + point.x += dx; + point.y += dy; + } + this.sprite.left = this.points[0].x; + this.sprite.top = this.points[0].y; +}; + +SpriteShape.prototype.fill = function (context) { }; + +SpriteShape.prototype.setPolygonPoints = function() { + this.points.push(new Point(this.x, this.y)); + this.points.push(new Point(this.x + this.sprite.width, this.y)); + this.points.push(new Point(this.x + this.sprite.width, this.y + this.sprite.height)); + this.points.push(new Point(this.x, this.y + this.sprite.height)); +}; + +SpriteShape.prototype.stroke = function (context) { + this.sprite.paint(context); +}; diff --git a/canvas/shared/js/slider.js b/canvas/shared/js/slider.js new file mode 100644 index 0000000..226b382 --- /dev/null +++ b/canvas/shared/js/slider.js @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +var COREHTML5 = COREHTML5 || {} + +// Constructor........................................................ + +COREHTML5.Slider = function(strokeStyle, fillStyle, + knobPercent, hpercent, vpercent) { + this.trough = new COREHTML5.RoundedRectangle(strokeStyle, fillStyle, + hpercent || 95, // horizontal size percent + vpercent || 55); // vertical size percent + + this.knobPercent = knobPercent || 0; + this.strokeStyle = strokeStyle ? strokeStyle : 'gray'; + this.fillStyle = fillStyle ? fillStyle : 'skyblue'; + + this.SHADOW_COLOR = 'rgba(100, 100, 100, 0.8)'; + this.SHADOW_OFFSET_X = 3; + this.SHADOW_OFFSET_Y = 3; + + this.HORIZONTAL_MARGIN = 2 * this.SHADOW_OFFSET_X; + this.VERTICAL_MARGIN = 2 * this.SHADOW_OFFSET_Y; + + this.KNOB_SHADOW_COLOR = 'yellow'; + this.KNOB_SHADOW_OFFSET_X = 1; + this.KNOB_SHADOW_OFFSET_Y = 1; + this.KNOB_SHADOW_BLUR = 0; + + this.KNOB_FILL_STYLE = 'rgba(255,255,255,0.45)'; + this.KNOB_STROKE_STYLE = 'rgba(0, 0, 150, 0.45)'; + + this.context = document.createElement('canvas').getContext('2d'); + this.changeEventListeners = []; + + this.createDOMElement(); + this.addMouseHandlers(); + + return this; +} + +// Prototype.......................................................... + +COREHTML5.Slider.prototype = { + + // General functions to override................................... + + createDOMElement: function () { + this.domElement = document.createElement('div'); + this.domElement.appendChild(this.context.canvas); + }, + + appendTo: function (elementName) { + document.getElementById(elementName). + appendChild(this.domElement); + + this.setCanvasSize(); + this.resize(); + }, + + setCanvasSize: function () { + var domElementParent = this.domElement.parentNode; + + this.context.canvas.width = domElementParent.offsetWidth; + this.context.canvas.height = domElementParent.offsetHeight; + }, + + resize: function() { + this.cornerRadius = (this.context.canvas.height/2 - + 2*this.VERTICAL_MARGIN)/2; + + this.top = this.HORIZONTAL_MARGIN; + this.left = this.VERTICAL_MARGIN; + + this.right = this.left + this.context.canvas.width - + 2*this.HORIZONTAL_MARGIN; + + this.bottom = this.top + this.context.canvas.height - + 2*this.VERTICAL_MARGIN; + + this.trough.resize(this.context.canvas.width, + this.context.canvas.height); + + this.knobRadius = this.context.canvas.height/2 - + this.context.lineWidth*2; + }, + + // Event Handlers.................................................. + + addMouseHandlers: function() { + var slider = this; // Let div's event handlers access this object + + this.domElement.onmouseover = function(e) { + slider.context.canvas.style.cursor = 'crosshair'; + }; + + this.domElement.onmousedown = function(e) { + var mouse = slider.windowToCanvas(e.clientX, e.clientY); + + e.preventDefault(); + + if (slider.mouseInTrough(mouse) || + slider.mouseInKnob(mouse)) { + + slider.knobPercent = slider.knobPositionToPercent(mouse.x); + slider.fireChangeEvent(e); + slider.erase(); + slider.draw(); + slider.dragging = true; + + } + }; + + window.addEventListener('mousemove', function(e) { + var mouse = null, + percent = null; + + e.preventDefault(); + + if (slider.dragging) { + mouse = slider.windowToCanvas(e.clientX, e.clientY); + percent = slider.knobPositionToPercent(mouse.x); + + if (percent >= 0 && percent <= 1.0) { + slider.fireChangeEvent(e); + slider.erase(); + slider.draw(percent); + } + } + }, false); + + window.addEventListener('mouseup', function(e) { + var mouse = null; + + e.preventDefault(); + + if (slider.dragging) { + slider.fireChangeEvent(e); + slider.dragging = false; + } + }, false); + }, + + // Change Events................................................... + + fireChangeEvent: function(e) { + for (var i=0; i < this.changeEventListeners.length; ++i) { + this.changeEventListeners[i](e); + } + }, + + addChangeListener: function (listenerFunction) { + this.changeEventListeners.push(listenerFunction); + }, + + // Utility Functions............................................... + + mouseInKnob: function(mouse) { + var position = this.knobPercentToPosition(this.knobPercent); + this.context.beginPath(); + this.context.arc(position, this.context.canvas.height/2, + this.knobRadius, 0, Math.PI*2); + + return this.context.isPointInPath(mouse.x, mouse.y); + }, + + mouseInTrough: function(mouse) { + this.context.beginPath(); + this.context.rect(this.left, 0, + this.right - this.left, this.bottom); + + return this.context.isPointInPath(mouse.x, mouse.y); + }, + + windowToCanvas: function(x, y) { + var bbox = this.context.canvas.getBoundingClientRect(); + + return { + x: x - bbox.left * (this.context.canvas.width / bbox.width), + y: y - bbox.top * (this.context.canvas.height / bbox.height) + }; + }, + + knobPositionToPercent: function(position) { + var troughWidth = this.right - this.left - 2*this.knobRadius; + return (position - this.left - this.knobRadius)/ troughWidth; + }, + + knobPercentToPosition: function(percent) { + if (percent > 1) percent = 1; + if (percent < 0) percent = 0; + var troughWidth = this.right - this.left - 2*this.knobRadius; + return percent * troughWidth + this.left + this.knobRadius; + }, + + // Drawing Functions............................................... + + fillKnob: function (position) { + this.context.save(); + + this.context.shadowColor = this.KNOB_SHADOW_COLOR; + this.context.shadowOffsetX = this.KNOB_SHADOW_OFFSET_X; + this.context.shadowOffsetY = this.KNOB_SHADOW_OFFSET_Y; + this.context.shadowBlur = this.KNOB_SHADOW_BLUR; + + this.context.beginPath(); + + this.context.arc(position, + this.top + ((this.bottom - this.top) / 2), + this.knobRadius, 0, Math.PI*2, false); + + this.context.clip(); + + this.context.fillStyle = this.KNOB_FILL_STYLE; + this.context.fill(); + this.context.restore(); + }, + + strokeKnob: function () { + this.context.save(); + this.context.lineWidth = 1; + this.context.strokeStyle = this.KNOB_STROKE_STYLE; + this.context.stroke(); + this.context.restore(); + }, + + drawKnob: function (percent) { + if (percent < 0) percent = 0; + if (percent > 1) percent = 1; + + this.knobPercent = percent; + this.fillKnob(this.knobPercentToPosition(percent)); + this.strokeKnob(); + }, + + drawTrough: function () { + this.context.save(); + this.trough.fillStyle = this.fillStyle; + this.trough.strokeStyle = this.strokeStyle; + this.trough.draw(this.context); + this.context.restore(); + }, + + draw: function (percent) { + this.context.globalAlpha = this.opacity; + + if (percent === undefined) { + percent = this.knobPercent; + } + + this.drawTrough(); + this.drawKnob(percent); + }, + + erase: function() { + this.context.clearRect( + this.left - this.knobRadius, 0 - this.knobRadius, + this.context.canvas.width + 4*this.knobRadius, + this.context.canvas.height + 3*this.knobRadius); + } +}; diff --git a/canvas/shared/js/sprites.js b/canvas/shared/js/sprites.js new file mode 100644 index 0000000..340e8ce --- /dev/null +++ b/canvas/shared/js/sprites.js @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Painters................................................................... + +// Painters paint sprites with a paint(sprite, context) method. ImagePainters +// paint an image for their sprite. + +var ImagePainter = function (imageUrl) { + this.image = new Image; + this.image.src = imageUrl; +}; + +ImagePainter.prototype = { + image: undefined, + + paint: function (sprite, context) { + if (this.image !== undefined) { + if ( ! this.image.complete) { + this.image.onload = function (e) { + sprite.width = this.width; + sprite.height = this.height; + + context.drawImage(this, // this is image + sprite.left, sprite.top, + sprite.width, sprite.height); + }; + } + else { + context.drawImage(this.image, sprite.left, sprite.top, + sprite.width, sprite.height); + } + } + } +}; + +SpriteSheetPainter = function (cells) { + this.cells = cells; +}; + +SpriteSheetPainter.prototype = { + cells: [], + cellIndex: 0, + + advance: function () { + if (this.cellIndex == this.cells.length-1) { + this.cellIndex = 0; + } + else { + this.cellIndex++; + } + }, + + paint: function (sprite, context) { + var cell = this.cells[this.cellIndex]; + context.drawImage(spritesheet, cell.left, cell.top, + cell.width, cell.height, + sprite.left, sprite.top, + cell.width, cell.height); + } +}; + +// Sprite Animators........................................................... + +// Sprite animators have an array of painters that they succesively apply +// to a sprite over a period of time. Animators can be started with +// start(sprite, durationInMillis, restoreSprite) + +var SpriteAnimator = function (painters, elapsedCallback) { + this.painters = painters; + if (elapsedCallback) { + this.elapsedCallback = elapsedCallback; + } +}; + +SpriteAnimator.prototype = { + painters: [], + duration: 1000, + startTime: 0, + index: 0, + elapsedCallback: undefined, + + end: function (sprite, originalPainter) { + sprite.animating = false; + + if (this.elapsedCallback) { + this.elapsedCallback(sprite); + } + else { + sprite.painter = originalPainter; + } + }, + + start: function (sprite, duration) { + var endTime = +new Date() + duration, + period = duration / (this.painters.length), + interval = undefined, + animator = this, // for setInterval() function + originalPainter = sprite.painter; + + this.index = 0; + sprite.animating = true; + sprite.painter = this.painters[this.index]; + + interval = setInterval(function() { + if (+new Date() < endTime) { + sprite.painter = animator.painters[++animator.index]; + } + else { + animator.end(sprite, originalPainter); + clearInterval(interval); + } + }, period); + }, +}; + +// Sprites.................................................................... + +// Sprites have a name, a painter, and an array of behaviors. Sprites can +// be updated, and painted. +// +// A sprite's painter paints the sprite: paint(sprite, context) +// A sprite's behavior executes: execute(sprite, context, time) + +var Sprite = function (name, painter, behaviors) { + if (name !== undefined) this.name = name; + if (painter !== undefined) this.painter = painter; + if (behaviors !== undefined) this.behaviors = behaviors; + + return this; +}; + +Sprite.prototype = { + left: 0, + top: 0, + width: 10, + height: 10, + velocityX: 0, + velocityY: 0, + visible: true, + animating: false, + painter: undefined, // object with paint(sprite, context) + behaviors: [], // objects with execute(sprite, context, time) + + paint: function (context) { + if (this.painter !== undefined && this.visible) { + this.painter.paint(this, context); + } + }, + + update: function (context, time) { + for (var i = this.behaviors.length; i > 0; --i) { + this.behaviors[i-1].execute(this, context, time); + } + } +}; diff --git a/canvas/shared/js/stopwatch.js b/canvas/shared/js/stopwatch.js new file mode 100644 index 0000000..2213d1e --- /dev/null +++ b/canvas/shared/js/stopwatch.js @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 David Geary. This code is from the book + * Core HTML5 Canvas, published by Prentice-Hall in 2012. + * + * License: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * The Software may not be used to create training material of any sort, + * including courses, books, instructional videos, presentations, etc. + * without the express written consent of David Geary. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Stopwatch.................................................................. +// +// Like the real thing, you can start and stop a stopwatch, and you can +// find out the elapsed time the stopwatch has been running. After you stop +// a stopwatch, it's getElapsedTime() method returns the elapsed time +// between the start and stop. +// +// Stopwatches are used primarily for timing animations. + +Stopwatch = function () { +}; + +// You can get the elapsed time while the timer is running, or after it's +// stopped. + +Stopwatch.prototype = { + startTime: 0, + running: false, + elapsed: undefined, + + start: function () { + this.startTime = +new Date(); + this.elapsedTime = undefined; + this.running = true; + }, + + stop: function () { + this.elapsed = (+new Date()) - this.startTime; + this.running = false; + }, + + getElapsedTime: function () { + if (this.running) { + return (+new Date()) - this.startTime; + } + else { + return this.elapsed; + } + }, + + isRunning: function() { + return this.running; + }, + + reset: function() { + this.elapsed = 0; + } +}; diff --git a/ex/ex1.html b/ex/ex1.html new file mode 100644 index 0000000..b87b507 --- /dev/null +++ b/ex/ex1.html @@ -0,0 +1,259 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/ex/ex2.html b/ex/ex2.html new file mode 100644 index 0000000..5608376 --- /dev/null +++ b/ex/ex2.html @@ -0,0 +1,379 @@ + + + + Example + + + + + + + Canvas not supported + + + + diff --git a/result/sample01.html b/result/sample01.html new file mode 100644 index 0000000..e49f968 --- /dev/null +++ b/result/sample01.html @@ -0,0 +1,271 @@ + + + + + Title + + + +Wellcome to jsWorld! + +
+

A

+ Line color: + + X: + + Y: + + + SCALE: + + + + +

B

+ Line color: + + X: + + Y: + +

+ + + +

+

+ + + Canvas not supported + + + + + + \ No newline at end of file