# Software Studio 2020 Spring ## Assignment 01 Web Canvas ## [Web Canvas link](https://106030009.gitlab.io/AS_01_WebCanvas) ### Scoring | **Basic components** | **Score** | **Check** | | :----------------------------------------------- | :-------: | :-------: | | Basic control tools | 30% | Y | | Text input | 10% | Y | | Cursor icon | 10% | Y | | Refresh button | 10% | Y | | **Advanced tools** | **Score** | **Check** | | :----------------------------------------------- | :-------: | :-------: | | Different brush shapes | 15% | Y | | Un/Re-do button | 10% | Y | | Image tool | 5% | Y | | Download | 5% | Y | | **Other useful widgets** | **Score** | **Check** | | :----------------------------------------------- | :-------: | :-------: | | Name of widgets | 1~5% | Y | --- # How to use the web canvas ## Layout : ![Layout of Web application](https://i.imgur.com/2BdGkER.png) <font size=4> 1.You can use Basic Tools Default is Pencil 2.You can change color by click on the color panel. 3.You can adjust the brush size. 4.You can change the background music. (4 songs) 5.You can type in text by first click the text icon, then click on the canvas at a location you want to type text. 6.You can draw shapes by clicking on the icons.(Circles, Rectangles, Triangles.) 7.You can upload your image from you local computer. 8.You can download you artwork into image file. 9.You can reset the canvas. 10.You can undo / redo actions if you want. </font> ### Basic Control Tools: <font size=4> The default tool is pencil. You can change the width of the pencil by **pulling the slide bar** on the right top part. The range is from 1 to 50. If you want to **erase** your canvas, just **click the eraser icon** and you can erase. ![Pencil image](https://i.imgur.com/T9MLXRL.png =200x200)![Basic Tools image](https://i.imgur.com/1D7U9Xi.png =200x200) </font> #### HTML: ```html= <div id="Basic-Tool"> <h3>Basic Tools</h3> <input type="image" src="pencil.png" height="68" width="68" name="brush" id="brush" onclick="changeCursor('pen_cursor.png')"/> <input type="image" src="eraser.png" height="68" width="68" name="eraser" id="eraser" onclick="changeCursor('erase_cursor.png')"/> <hr> </div> ``` #### javaScript: ```javascript= // Draw Pencil or Other Objects. (during mouseMove). function brushDraw(canvas, positionX, positionY) { if(mouseIsDown) { ctx.lineTo(positionX, positionY); ctx.stroke(); if(isEraser) { document.body.style.cursor = 'url("erase_cursor.png"),auto'; } else { document.body.style.cursor = 'url("brush_cursor.png"),auto'; } } } ``` ### Text Input <font size=4> If you want to type text on your canvas, follow steps below: 1. Click the 'Text' icon. 2. Click a location on canvas where you want to type. 3. Type text in the input box. 4. Press 'Enter' key, then done. ![](https://i.imgur.com/odHC3Wa.png =200x200) **Note** : Once you open up an input text box, you should finish adding text, otherwise, you **CANNOT** reset. If you find you cannot type in words, make sure you have **click the text box** and the text **cursor is blinking.** ### Implementation : * Create a **Canvas array.** * Every time we've done an action, **Push** the **canvas information** into the Canvas array. * When **undo**, go to the **last timestep canvas.** * When **redo**, go to the **next timestep canvas </font> #### HTML: ```html= <div id= "Font-Selector"> <h3>Font Style and Size: </h3> <input type="image" src="text.png" id='text' height="80" width="80" onclick="Type_Text();changeCursor('text')"/> <select onChange="fontFam(value)" id="fontFamily"> <option value="Arial">Arial</option> <option value="Times New Roman">Times New Roman</option> <option value="Lucida Console">Lucida Console</option> <option value="fantasy">fantasy</option> <option value="cursive">cursive</option> </select> <select onChange="fontSize(value)" id="fontSize"> <option value="12">12</option> <option value="16">16</option> <option value="20">20</option> <option value="32">32</option> <option value="40">40</option> <option value="48">48</option> <option value="56">56</option> <option value="60">60</option> <option value="100">100</option> </select> <br> <br> <hr> </div> ``` ### javaScript: ```javascript= function Type_Text(){ // Click Text icon. isPen = false; isEraser = false; isCircle = false; isRect = false; isTri = false; isRectFill = false; isCircleFill = false; isTriFill = false; isText = true; document.body.style.cursor = 'text'; } // Default setting. var hasInput = false; var font_family = "Arial"; var font_size = "12px"; function fontFam(val){ font_family = val; console.log("Change fontFam"); } function fontSize(val){ font_size = val; console.log("Change fontSize"); } // Click on canvas. tempCanvas.onclick = function(e) { var rect = canvas.getBoundingClientRect(); if (hasInput || isText !== true) return; addInput(e.clientX /*- rect.left*/ , e.clientY /*- rect.top*/); }; function addInput(x, y) { var input = document.createElement('input'); input.type = 'text'; input.style.position = 'fixed'; input.style.left = (x) + 'px'; input.style.top = (y) + 'px'; input.onkeydown = handleEnter; document.body.appendChild(input); // input.focus(); hasInput = true; }; function handleEnter(e) { var keyCode = e.keyCode; if (keyCode === 13) { //New line. drawText(this.value, parseInt(this.style.left, 10), parseInt(this.style.top, 10)); document.body.removeChild(this); hasInput = false; } }; function drawText(txt, x, y) { ctx.textBaseline = 'top'; ctx.textAlign = 'left'; ctx.font = font_size + "px " + font_family; console.log(font_size); console.log(font_family); console.log(x); console.log(y); ctx.fillText(txt, x-150 , y-35 ); cPush(); }; ``` ### Cursor icon <font size=4> I use a changeCursor(url) function in javaScript file. 1.document.getElementById() to get **HTML elements.** 2.Whenever the icons are clicked, **change the cursor to the corresponding icon.** </font> ### Implementation #### HTML ```HTML = <div id="Basic-Tool"> <h3>Basic Tools</h3> <input type="image" src="pencil.png" height="68" width="68" name="brush" id="brush" onclick="changeCursor('pen_cursor.png')"/> <input type="image" src="eraser.png" height="68" width="68" name="eraser" id="eraser" onclick="changeCursor('erase_cursor.png')"/> <hr> </div> <div id="Shape-Select"> <h3>Fun Shapes: </h3> <input type="image" src="rectangle_line.png" height="60" width="60" name="rectangle" id="rectangle" onclick="changeCursor('rec_cursor.png')" /> <input type="image" src="circle_line.png" height="60" width="60" name="circle" id="circle" onclick="changeCursor('circle_cursor.png')" /> <input type="image" src="triangle_line.png" height="60" width="60" name="triangle" id="triangle" onclick="changeCursor('tri_cursor.png')" /> <input type="image" src="rectangle.png" height="60" width="60" name="rectangle_fill" id="rectangle_fill" onclick="changeCursor('rec_cursor.png')" /> <input type="image" src="circle.png" height="60" width="60" name="circle_fill" id="circle_fill" onclick="changeCursor('circle_cursor.png')" /> <input type="image" src="triangle.png" height="60" width="60" name="triangle_fill" id="triangle_fill" onclick="changeCursor('tri_cursor.png')" /> <br> <br> <hr> </div> ``` ### javaScript ``` javaScript= function changeCursor(url){ if(url == 'text'){ canvas.style.cursor = url; tempCanvas.style.cursor = url; } else{ canvas.style.cursor = "url("+url+"),auto"; tempCanvas.style.cursor = "url("+url+"),auto"; } ; ``` ## Reset / Undo / Redo <font size=4> The **RESET button** is at left top of the web page. ![](https://i.imgur.com/qnNeonu.png =200x200) Their is an **undo button** at the **left bottom** side of canvas. ![](https://i.imgur.com/CJMWidu.png =200x200) The **redo button** is at the **right bottom** side of the canvas. ![](https://i.imgur.com/qdiLvR2.jpg =200x200) ### Implementation : * Create a **Canvas array.** * Every time we've done an action, **Push** the **canvas information** into the Canvas array. * When **undo**, go to the **last timestep canvas.** * When **redo**, go to the **next timestep canvas #### javaScript: ```javascript= var cPushArray = new Array(); var cStep = -1; function resetClick() { ctx.clearRect(0, 0, canvas.width, canvas.height); cPush.length = 0; // clear cPushArray. cPush(); // Record this 'reset' action. cStep = -1; // -1 means. we don't undo. } function cPush() { console.log("Push!"); console.log(cStep); cStep++; if (cStep < cPushArray.length) { cPushArray.length = cStep; } cPushArray.push(document.getElementById('canvas').toDataURL()); // Push every timestep of Canvas into this Big Stack. (First-in Last-out.) } function cUndo() { if (cStep > 0) { // we can undo. cStep--; var canvasPic = new Image(); //every time new an Image(); canvasPic.src = cPushArray[cStep]; //Get last moment canvas information. ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the whold canvas. canvasPic.onload = function () { ctx.drawImage(canvasPic, 0, 0); } // Place our last result on it. // .onload : use for waiting. } else if (cStep == 0) { //Let cstep be -1. ctx.clearRect(0, 0, canvas.width, canvas.height); cStep--; } else{ console.log(" cStep < 0!"); } } function cRedo() { if (cStep < cPushArray.length-1) { // the Latest moment, we cannot Redo! Since we don't know the future. cStep++; // move to next moment. var canvasPic = new Image(); // new an Image canvasPic.src = cPushArray[cStep]; ///Get next moment canvas information. canvasPic.onload = function () { ctx.drawImage(canvasPic, 0, 0); } // Place our next result on it. } } ``` ## Upload / Download Image You can upload your personal unique/ favorite images or photos by clicking the **Upload button** at **the lowest part of the toolbar.** And if you want to download your masterpiece, just click **Download button** beside the upload button, you'll get an image file of your artwork. ![](https://i.imgur.com/CA1bnTW.png =200x200)![](https://i.imgur.com/XvEzglQ.png =230x230) ### Changing Colors You can change the brush color to any color you like. 1.First click the small **color-label**. 2.Then, a **color panel** would pop out. 3.Pick a color you like. You can click the **side color strip** to change to a different color family. 4.Click a point on the **color panel** then your color would change to it. You can **verify the final color via the color-label.** ![](https://i.imgur.com/Zvo1UYf.png =300x300) ### Implementation #### HTML: ```html= <div id="color-selector"> <h3>Color Selector</h3> <!-- Display your current color. --> <label for="color-input" id="color-label" style="background-color: red"></label> <input type="checkbox" id="color-input" checked ></input> <div id="color-picker"> <canvas id="color-block" height="150" width="150"></canvas> <canvas id="color-strip" height="150" width="30"></canvas> </div> </div> ``` ### CSS: ```css= #color-selector{ width: 50%; float: right; text-align: left; /* background:rgba(12, 175, 140, 0.7); */ } /* Color Selector */ @import url(https://fonts.googleapis.com/css?family=Open+Sans); h2 { background-color: #dbdbdb; margin: 0; margin-bottom: 15px; padding: 10px; font-family: 'Open Sans'; } #color-input { display: none; float: left; } #color-label { /* position: absolute; */ float: left; border: solid 1px blanchedalmond; margin-left: 25px; height: 30px; width: 50px; } #color-input:checked ~ #color-picker { float: right; opacity: 1; } #color-picker { float: right; /* position: relative; */ top: 0px; left: 80px; background-color: white; height: 170px; width: 200px; border: solid 1px #ccc; opacity: 0; padding: 6px; } #color-picker:hover { cursor: crosshair; } ``` #### javaScript: ```javascript= /////////// Color Selector //////////////// var colorLabel = document.getElementById('color-label'); var colorBlock = document.getElementById('color-block'); var ctx1 = colorBlock.getContext('2d'); var width1 = colorBlock.width; var height1 = colorBlock.height; var colorStrip = document.getElementById('color-strip'); var ctx2 = colorStrip.getContext('2d'); var width2 = colorStrip.width; var height2 = colorStrip.height; var x = 0; var y = 0; var drag = false; var rgbaColor = 'rgba(255,0,0,1)'; ctx1.rect(0, 0, width1, height1); fillGradient(); ctx2.rect(0, 0, width2, height2); var grd1 = ctx2.createLinearGradient(0, 0, 0, height1); grd1.addColorStop(0, 'rgba(255, 0, 0, 1)'); grd1.addColorStop(0.17, 'rgba(255, 255, 0, 1)'); grd1.addColorStop(0.34, 'rgba(0, 255, 0, 1)'); grd1.addColorStop(0.51, 'rgba(0, 255, 255, 1)'); grd1.addColorStop(0.68, 'rgba(0, 0, 255, 1)'); grd1.addColorStop(0.85, 'rgba(255, 0, 255, 1)'); grd1.addColorStop(1, 'rgba(255, 0, 0, 1)'); ctx2.fillStyle = grd1; ctx2.fill(); colorStrip.addEventListener("click", click, false); colorBlock.addEventListener("mousedown", Color_mousedown, false); colorBlock.addEventListener("mousemove", Color_mousemove, false); colorBlock.addEventListener("mouseup", Color_mouseup, false); function click(e) { x = e.offsetX; y = e.offsetY; var imageData = ctx2.getImageData(x, y, 1, 1).data; rgbaColor = 'rgba(' + imageData[0] + ',' + imageData[1] + ',' + imageData[2] + ',1)'; colorLabel.style.backgroundColor = rgbaColor; ctx.strokeStyle = rgbaColor; fillGradient(); } function fillGradient() { ctx1.fillStyle = rgbaColor; ctx1.fillRect(0, 0, width1, height1); var grdWhite = ctx2.createLinearGradient(0, 0, width1, 0); grdWhite.addColorStop(0, 'rgba(255,255,255,1)'); grdWhite.addColorStop(1, 'rgba(255,255,255,0)'); ctx1.fillStyle = grdWhite; ctx1.fillRect(0, 0, width1, height1); var grdBlack = ctx2.createLinearGradient(0, 0, 0, height1); grdBlack.addColorStop(0, 'rgba(0,0,0,0)'); grdBlack.addColorStop(1, 'rgba(0,0,0,1)'); ctx1.fillStyle = grdBlack; ctx1.fillRect(0, 0, width1, height1); } function Color_mousedown(e) { drag = true; changeColor(e); } function Color_mousemove(e) { if (drag) { changeColor(e); } } function Color_mouseup(e) { drag = false; } function changeColor(e) { x = e.offsetX; y = e.offsetY; var imageData = ctx1.getImageData(x, y, 1, 1).data; rgbaColor = 'rgba(' + imageData[0] + ',' + imageData[1] + ',' + imageData[2] + ',1)'; colorLabel.style.backgroundColor = rgbaColor; ctx.strokeStyle = rgbaColor; // For text. ctx.fillStyle = rgbaColor; } ``` ## Drawing Shapes You can draw either **Circles, Rectangles, or Triangles** with **different styles (Fill / Line).** Just click the shape you want and draw! ![](https://i.imgur.com/BVSRwW6.png =200x200)![](https://i.imgur.com/FAD55GQ.png =200x200)![](https://i.imgur.com/fBk2g0L.png =200x200)![](https://i.imgur.com/hm15VTx.png =200x200)![](https://i.imgur.com/1m8xyZJ.png =200x200)![](https://i.imgur.com/mJwWGpl.png =200x200) ### Implementation #### HTML: ```html= <div id="Shape-Select"> <h3>Fun Shapes: </h3> <input type="image" src="rectangle_line.png" height="60" width="60" name="rectangle" id="rectangle" onclick="changeCursor('rec_cursor.png')" /> <input type="image" src="circle_line.png" height="60" width="60" name="circle" id="circle" onclick="changeCursor('circle_cursor.png')" /> <input type="image" src="triangle_line.png" height="60" width="60" name="triangle" id="triangle" onclick="changeCursor('tri_cursor.png')" /> <input type="image" src="rectangle.png" height="60" width="60" name="rectangle_fill" id="rectangle_fill" onclick="changeCursor('rec_cursor.png')" /> <input type="image" src="circle.png" height="60" width="60" name="circle_fill" id="circle_fill" onclick="changeCursor('circle_cursor.png')" /> <input type="image" src="triangle.png" height="60" width="60" name="triangle_fill" id="triangle_fill" onclick="changeCursor('tri_cursor.png')" /> <br> <br> <hr> </div> ``` #### javaScript: ```javascript= function draw(final) { if (final == true) { // final draw ctx = context; // clear temp canvas tempContext.clearRect(0, 0, canvas.width, canvas.height); } else { // temporary draw ctx = tempContext; // clear true canvas ctx.clearRect(0, 0, canvas.width, canvas.height); } //Update color. ctx.strokeStyle = rgbaColor; ctx.fillStyle = rgbaColor; // Calculating parameters. var w = endX - startX; var h = endY - startY; var offsetX = (w < 0) ? w : 0; var offsetY = (h < 0) ? h : 0; var width = Math.abs(w); var height = Math.abs(h); var radius = Math.sqrt(Math.pow((startX - endX), 2) + Math.pow((startY - endY), 2)) ctx.lineWidth = slider.value; if(isCircle) { ctx.beginPath(); ctx.arc(startX + offsetX, startY + offsetY, radius, 0, 2 * Math.PI); ctx.closePath(); ctx.stroke(); } if(isRect) { ctx.beginPath(); ctx.rect(startX, startY , w, h); ctx.closePath(); ctx.stroke(); } if(isTri) { ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(startX + w, startY + h); ctx.lineTo(startX - w, startY + h); ctx.closePath(); ctx.stroke(); } if(isCircleFill) { ctx.beginPath(); ctx.arc(startX + offsetX, startY + offsetY, radius, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); } if(isRectFill) { ctx.beginPath(); ctx.rect(startX, startY , w, h); ctx.closePath(); ctx.fill(); } if(isTriFill) { ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(startX + w, startY + h); ctx.lineTo(startX - w, startY + h); ctx.closePath(); ctx.fill(); } console.log("here"); } ``` </font> # Other Function : ## Music Selection ![](https://i.imgur.com/sJSCYi9.png ) <font size=4> You can choose different **background music** to add more spice to your recreation time! There're **four different music** for you to choose. Feel free to change any song you like or **mute** it if you need to concentrate. </font> # Function Describtion ## Build Canvas <font size=4> 1. Declare 2 <canvas> tags in HTML file. 2. Get elements in .js and declare 2D context. 3. Add eventListeners to canvas objects. </font> ## Onclick function <font size=4> 1. When an icon is clicked, invoke functions. 2. Change mode to that object, for example, click 'Circle', let isCircle = true; other flags set to false. 3. When mousedown/ move/ up, call draw(), and draw the corresponding object on canvas. ## How to draw different shapes. <font size=4> 1. Declare getMousePos() to get mouse position. 2. Need start point (when mouse is pressed down) and end point(when mouse is UP) to draw shapes. 3. If 'Circle', use ctx.arc(startX + offsetX, startY + offsetY, radius, 0, 2 * Math.PI); If 'Rectangle', use ctx.rect(startX, startY , w, h); If 'Triangle', use ctx.lineTo(startX + w, startY + h); ctx.lineTo(startX - w, startY + h); </font> ### Full draw(final) function : ```javascript= var w = endX - startX; var h = endY - startY; var offsetX = (w < 0) ? w : 0; var offsetY = (h < 0) ? h : 0; var width = Math.abs(w); var height = Math.abs(h); var radius = Math.sqrt(Math.pow((startX - endX), 2) + Math.pow((startY - endY), 2)) ctx.lineWidth = slider.value; if(isCircle) { ctx.beginPath(); ctx.arc(startX + offsetX, startY + offsetY, radius, 0, 2 * Math.PI); ctx.closePath(); ctx.stroke(); } if(isRect) { ctx.beginPath(); ctx.rect(startX, startY , w, h); ctx.closePath(); ctx.stroke(); } if(isTri) { ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(startX + w, startY + h); ctx.lineTo(startX - w, startY + h); ctx.closePath(); ctx.stroke(); } if(isCircleFill) { ctx.beginPath(); ctx.arc(startX + offsetX, startY + offsetY, radius, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); } if(isRectFill) { ctx.beginPath(); ctx.rect(startX, startY , w, h); ctx.closePath(); ctx.fill(); } if(isTriFill) { ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(startX + w, startY + h); ctx.lineTo(startX - w, startY + h); ctx.closePath(); ctx.fill(); } ``` # Gitlab page link <font size=5> https://106030009.gitlab.io/AS_01_WebCanvas </font> # Others (Optional) <font size=5> Several question: 1. Why my web canvas **cannot get .jpg resources?** (if I use .png, it works.) 2. I've try to use ```javascript= document.addEventListener('keydown', function(e)=>{ if(e.keycode==37){ //Left arrow BrushSize.innerHTML -= 1; ctx.lineWidth -= 1; slider.value -=1; } else if(e.keycode==39){ //Right arrow BrushSize.innerHTML += 1; ctx.lineWidth += 1; slider.value +=1; } }) ``` and use keyup to complete the task (use keyboard to change the Brush size.) However, the result is not as expected. I've try so many time including console.log(slider.value) to debug and find the log value correct, but the webpage just don't act correctly, this really bothers me a lot. TAs you are so 辛苦 and I really appreciate for your devotion and assistance. Although I'm a slow learner and kinda stupid on programming, I try my best to absorb every knowledge and try to implement what I've learned. Thank's for grading my assinment 01! Have a nice day and hope you enjoy the music I picked. :) </font> <style> table th{ width: 100%; } </style> --- ###### tags: `Software Studio` `Software Lab` `軟體設計與實驗` `軟實`