From 337bcec73ad05dbf439c97d13ebc2e97dd763d9b Mon Sep 17 00:00:00 2001 From: xylo Date: Mon, 23 Mar 2026 19:32:01 +0530 Subject: [PATCH 01/33] bugfix : button clicking UI and cursor appearance --- apollo.css | 24 +++++++++++++++++++----- apollo.html | 5 +++-- canvas.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 canvas.js diff --git a/apollo.css b/apollo.css index d01fff5..b70c7a7 100644 --- a/apollo.css +++ b/apollo.css @@ -18,6 +18,9 @@ } body { background-color: var(--bg-color) ; + padding: 0; + margin: 0 0 0 0; + overflow: hidden; } button { background: none; @@ -31,6 +34,12 @@ button { justify-content: center; } +.crosshair { + cursor : crosshair; +} +.textcursor { + cursor : text; +} #top_buttons { background-color: var(--button-background-color); position: absolute; @@ -51,9 +60,10 @@ button { .tools { flex-grow: 1; } -.tools:active { +.selected { background-color: var(--button-active-color); } + .tools:focus { outline: none; box-shadow: none; @@ -66,7 +76,7 @@ button { transition: stroke 0.3s ; } -.tools:focus svg, .tools:hover svg { +.tools:hover svg { stroke: var(--button-highlight-color); stroke-width: 3; } @@ -83,12 +93,12 @@ button { margin:0; background-color: var(--button-background-color); } -#toggle_theme:hover, #toggle_theme:focus { +#toggle_theme:hover { outline:none; box-shadow: none; } -#toggle_theme:hover svg, #toggle_theme:focus svg { +#toggle_theme:hover svg { stroke: black; stroke-width: 3; } @@ -107,7 +117,11 @@ button { display: none; } -#toggle_theme:hover svg, #toggle_theme:focus svg { +#toggle_theme:hover svg { stroke-color: var(--button-highlight-color); stroke-width: 2.5; } + +#paper { + z-index: -1; +} diff --git a/apollo.html b/apollo.html index 0622b08..6857305 100644 --- a/apollo.html +++ b/apollo.html @@ -7,10 +7,11 @@ - + + Uhm sorry!....An error occured while loading the canvas. @@ -32,7 +33,7 @@ - + diff --git a/canvas.js b/canvas.js new file mode 100644 index 0000000..dadf036 --- /dev/null +++ b/canvas.js @@ -0,0 +1,49 @@ +const canvas = document.getElementById('paper'); +const ctx = canvas.getContext("2d"); +canvas.width = window.innerWidth; +canvas.height = window.innerHeight; +/*add canvas resizing feature, store all canvas objects in an array*/ +/*function cursor_to_cross() { + document.body.classList.remove("textcursor"); + document.body.classList.toggle("crosshair"); +} +function cursor_to_text() { + document.body.classList.remove("crosshair"); + document.body.classList.toggle("textcursor"); +} +const tools = document.getElementsByClassName('tools') +for (const tool of tools) { + tool.addEventListener("click", cursor_to_cross); +} +const texttool = document.getElementById("text") +texttool.removeEventListener("click", cursor_to_cross); +texttool.addEventListener("click", cursor_to_text) */ + +let curr_tool = localStorage.getItem("curr_tool"); +if (curr_tool === null) { + localStorage.setItem("curr_tool", "selection"); + curr_tool = localStorage.getItem("curr_tool"); + canvas.classList.remove("crosshair", "textcursor"); + document.getElementById(curr_tool).classList.add("selected"); + console.log("first time"); +} +console.log(curr_tool); +const tools = document.getElementsByClassName('tools'); +for (const tool of tools) { + tool.addEventListener("click", (event) => { + document.getElementById(curr_tool).classList.remove("selected"); + console.log("${curr_tool} removed"); + localStorage.setItem("curr_tool", event.currentTarget.id); + curr_tool = localStorage.getItem("curr_tool"); + document.getElementById(curr_tool).classList.add("selected"); + console.log("${selected} added to ${curr_tool}"); + canvas.classList.remove("crosshair", "textcursor"); + if (curr_tool === "text") { + canvas.classList.add("textcursor"); + } + else { + canvas.classList.add("crosshair"); + } + }); +} + From 62ef93655ab0c1a39beedc5770a1d989201e7380 Mon Sep 17 00:00:00 2001 From: xylo Date: Mon, 23 Mar 2026 20:07:08 +0530 Subject: [PATCH 02/33] bugfix : Equality checking while accessing localStorage --- canvas.js | 47 +++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/canvas.js b/canvas.js index dadf036..d9571c5 100644 --- a/canvas.js +++ b/canvas.js @@ -3,47 +3,38 @@ const ctx = canvas.getContext("2d"); canvas.width = window.innerWidth; canvas.height = window.innerHeight; /*add canvas resizing feature, store all canvas objects in an array*/ -/*function cursor_to_cross() { - document.body.classList.remove("textcursor"); - document.body.classList.toggle("crosshair"); -} -function cursor_to_text() { - document.body.classList.remove("crosshair"); - document.body.classList.toggle("textcursor"); -} -const tools = document.getElementsByClassName('tools') -for (const tool of tools) { - tool.addEventListener("click", cursor_to_cross); -} -const texttool = document.getElementById("text") -texttool.removeEventListener("click", cursor_to_cross); -texttool.addEventListener("click", cursor_to_text) */ let curr_tool = localStorage.getItem("curr_tool"); -if (curr_tool === null) { +if (curr_tool === "") { localStorage.setItem("curr_tool", "selection"); curr_tool = localStorage.getItem("curr_tool"); canvas.classList.remove("crosshair", "textcursor"); - document.getElementById(curr_tool).classList.add("selected"); - console.log("first time"); } -console.log(curr_tool); + +//function to change the cursor +function change_cursor(){ + canvas.classList.remove("crosshair", "textcursor"); + if (curr_tool === "text") { + canvas.classList.add("textcursor"); + } + else if (curr_tool !== "selection") { + canvas.classList.add("crosshair"); + } +} + +document.getElementById(curr_tool).classList.add("selected"); +change_cursor(); + const tools = document.getElementsByClassName('tools'); for (const tool of tools) { tool.addEventListener("click", (event) => { document.getElementById(curr_tool).classList.remove("selected"); - console.log("${curr_tool} removed"); + console.log(`class selected removed from ${curr_tool}.`); localStorage.setItem("curr_tool", event.currentTarget.id); curr_tool = localStorage.getItem("curr_tool"); document.getElementById(curr_tool).classList.add("selected"); - console.log("${selected} added to ${curr_tool}"); - canvas.classList.remove("crosshair", "textcursor"); - if (curr_tool === "text") { - canvas.classList.add("textcursor"); - } - else { - canvas.classList.add("crosshair"); - } + console.log(`class selected added to ${curr_tool}`); + change_cursor(); }); } From 2276bd5804f31cc3ad00eb9064f1ebeec2a17624 Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 00:41:07 +0530 Subject: [PATCH 03/33] feature: basic drawing facilities --- apollo.css | 6 ++--- apollo.html | 2 -- canvas.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/apollo.css b/apollo.css index b70c7a7..fbf8024 100644 --- a/apollo.css +++ b/apollo.css @@ -59,6 +59,7 @@ button { .tools { flex-grow: 1; + z-index: 1; } .selected { background-color: var(--button-active-color); @@ -92,6 +93,7 @@ button { padding:0; margin:0; background-color: var(--button-background-color); + z-index: 1; } #toggle_theme:hover { outline:none; @@ -121,7 +123,3 @@ button { stroke-color: var(--button-highlight-color); stroke-width: 2.5; } - -#paper { - z-index: -1; -} diff --git a/apollo.html b/apollo.html index 6857305..994366c 100644 --- a/apollo.html +++ b/apollo.html @@ -17,8 +17,6 @@ - - diff --git a/canvas.js b/canvas.js index d9571c5..0a9e6f4 100644 --- a/canvas.js +++ b/canvas.js @@ -3,7 +3,9 @@ const ctx = canvas.getContext("2d"); canvas.width = window.innerWidth; canvas.height = window.innerHeight; /*add canvas resizing feature, store all canvas objects in an array*/ - +let is_drawing = false; +let startX = 0; +let startY = 0; let curr_tool = localStorage.getItem("curr_tool"); if (curr_tool === "") { localStorage.setItem("curr_tool", "selection"); @@ -38,3 +40,63 @@ for (const tool of tools) { }); } +//drawing functions : +function draw_line(endX,endY) { + ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.beginPath(); + ctx.moveTo(startX,startY); + ctx.lineTo(endX,endY); + ctx.stroke(); +} +function draw_rectangle(endX,endY) { + ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.strokeRect(startX,startY,endX-startX,endY-startY); +} + +function draw_ellipse(endX, endY) { + ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.beginPath(); + ctx.ellipse((startX+endX)/2, (endY+startY)/2,Math.abs((endX-startX)/2),Math.abs((endY-startY)/2),0,0,2*Math.PI); + ctx.stroke(); +} + +function draw_free(endX, endY) { + ctx.beginPath(); + ctx.moveTo(startX, startY); + ctx.lineTo(endX, endY); + ctx.stroke(); + startX = endX; + startY = endY; +} + +canvas.addEventListener("mousedown", (event) => { + is_drawing = true; + startX = event.clientX; + startY = event.clientY; +}) +canvas.addEventListener("mousemove", (event) => { + if (is_drawing === true) { + if(curr_tool === "line"){ + draw_line(event.clientX, event.clientY); + } + else if(curr_tool === "draw_rectangle"){ + draw_rectangle(event.clientX, event.clientY); + } + else if(curr_tool === "draw_circle"){ + draw_ellipse(event.clientX, event.clientY); + } + else if(curr_tool === "pencil"){ + draw_free(event.clientX, event.clientY); + } + + } +}) +canvas.addEventListener("mouseup", (event) => { + is_drawing = false; +}) + +//to prevent glitches when mouse leaves canvas +canvas.addEventListener("mouseleave", (event) => { + is_drawing = false; +}) +//bug to be fixed later : drawing stops when pointer crosses toolbar \ No newline at end of file From 7bdc8f3b32a1d0861449c299b0373d82e25158ab Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 12:24:52 +0530 Subject: [PATCH 04/33] feature: Ability to change stroke style and stroke colour --- apollo.css | 95 ++++++++++++++++++++++++++++++++++++++++++----------- apollo.html | 22 +++++++++++-- canvas.js | 65 +++++++++++++++++++++++++++++++++--- 3 files changed, 155 insertions(+), 27 deletions(-) diff --git a/apollo.css b/apollo.css index fbf8024..48a961c 100644 --- a/apollo.css +++ b/apollo.css @@ -42,7 +42,7 @@ button { } #top_buttons { background-color: var(--button-background-color); - position: absolute; + position: fixed; display: flex; align-items: stretch; gap: 1%; @@ -50,13 +50,12 @@ button { padding:0; width: clamp(12rem,50%,50rem); left: 25%; - top: 5%; + top: 0rem; right : 25%; height: 5%; border-radius: 8rem; border-style:none; } - .tools { flex-grow: 1; z-index: 1; @@ -82,10 +81,9 @@ button { stroke-width: 3; } -#toggle_theme { +.extra_functionality { position: fixed; - top:20px; - right:20px; + top:0.6875rem; height: 6vh; aspect-ratio: 1/1; display: flex; @@ -93,22 +91,23 @@ button { padding:0; margin:0; background-color: var(--button-background-color); - z-index: 1; + z-index:1; +} + +#undo { + left:20px; + fill:none; + stroke: var(--button-fill-color); +} +#toggle_theme { + right:20px; } -#toggle_theme:hover { +.extra_functionality:hover { outline:none; box-shadow: none; } -#toggle_theme:hover svg { - stroke: black; - stroke-width: 3; -} -#toggle_theme svg { - height:80%; - width:100%; - stroke: var(--button-fill-color) -} + #toggle_theme svg:last-child { display: none; } @@ -119,7 +118,63 @@ button { display: none; } -#toggle_theme:hover svg { - stroke-color: var(--button-highlight-color); - stroke-width: 2.5; +.extra_functionality:hover svg { + stroke: var(--button-highlight-color); + stroke-width: 1.5; +} + +#paper { + position: fixed; + top:0; + left:0; +} + +#stroke_manipulation{ + border:0; + margin:0; + padding:1rem 1rem 1rem 1rem; + display:flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: var(--button-background-color); + border-radius: 1rem; + position: fixed; + top: 20%; + height:auto; + max-height: 70vh; + gap: 0.7rem; + left:0rem; +} +#stroke_manipulation pre{ + margin:0; + font-size:1rem; + color: var(--button-fill-color); +} +#stroke_manipulation input{ + padding: 0; + margin:0; + border : none; +} +#stroke_manipulation input:hover{ + cursor: pointer; +} +#stroke_manipulation div{ + display: inline-flex; + align-items: center; + justify-content: center; + gap:1rem; +} +#stroke_manipulation div button{ + width:3rem; +} +#stroke_manipulation div button:hover svg{ + stroke: var(--button-highlight-color); + stroke-width: 2.70; +} +#stroke_manipulation div button svg{ + stroke: var(--button-fill-color); + height: 80%; + width:100%; + fill:none; } diff --git a/apollo.html b/apollo.html index 994366c..ac026b0 100644 --- a/apollo.html +++ b/apollo.html @@ -26,11 +26,29 @@ - + + + +
Stroke color:
+ + +
Stroke style:
+
+ + +
+
Stroke width:
+ +
Opacity:
+ +
diff --git a/canvas.js b/canvas.js index 0a9e6f4..e9dcaac 100644 --- a/canvas.js +++ b/canvas.js @@ -3,15 +3,38 @@ const ctx = canvas.getContext("2d"); canvas.width = window.innerWidth; canvas.height = window.innerHeight; /*add canvas resizing feature, store all canvas objects in an array*/ + let is_drawing = false; let startX = 0; let startY = 0; let curr_tool = localStorage.getItem("curr_tool"); -if (curr_tool === "") { +let stroke_color = localStorage.getItem("stroke_color"); +let stroke_style = localStorage.getItem("stroke_style"); +console.log(localStorage); + + +if (!curr_tool) { localStorage.setItem("curr_tool", "selection"); - curr_tool = localStorage.getItem("curr_tool"); + curr_tool = "selection"; canvas.classList.remove("crosshair", "textcursor"); } +if (!stroke_color) { + if(localStorage.getItem("light") === 'false') + localStorage.setItem("stroke_color", "#f5b811"); + else { + localStorage.setItem("stroke_color","#a60818"); + } + stroke_color = localStorage.getItem("stroke_color"); +} +if (!stroke_style) { + localStorage.setItem("stroke_style", "straight-line"); + stroke_style = "straight-line"; +} + +document.getElementById("stroke_color").value = stroke_color; +document.getElementById(curr_tool).classList.add("selected"); +document.getElementById(stroke_style).classList.add("selected"); +console.log(localStorage); //function to change the cursor function change_cursor(){ @@ -23,9 +46,20 @@ function change_cursor(){ canvas.classList.add("crosshair"); } } +//function to change the stroke style +function change_style(){ + if(stroke_style === "dotted-line"){ + ctx.setLineDash([2, 5]); + } + else{ + ctx.setLineDash([]); + } + ctx.lineDashOffset = 0; +} -document.getElementById(curr_tool).classList.add("selected"); change_cursor(); +change_style(); + const tools = document.getElementsByClassName('tools'); for (const tool of tools) { @@ -37,12 +71,25 @@ for (const tool of tools) { document.getElementById(curr_tool).classList.add("selected"); console.log(`class selected added to ${curr_tool}`); change_cursor(); - }); -} + })}; + +const stroke_buttons = document.getElementsByClassName('stroke_button'); +for (const butt of stroke_buttons){ + butt.addEventListener("click", (event) => { + document.getElementById(stroke_style).classList.remove("selected"); + console.log(`class selected removed from ${stroke_style}.`); + localStorage.setItem("stroke_style", event.currentTarget.id); + stroke_style = localStorage.getItem("stroke_style"); + document.getElementById(stroke_style).classList.add("selected"); + console.log(`class selected added to ${stroke_style}`); + change_style(); + } +)}; //drawing functions : function draw_line(endX,endY) { ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.strokeStyle = `${stroke_color}`; ctx.beginPath(); ctx.moveTo(startX,startY); ctx.lineTo(endX,endY); @@ -50,17 +97,20 @@ function draw_line(endX,endY) { } function draw_rectangle(endX,endY) { ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.strokeStyle = `${stroke_color}`; ctx.strokeRect(startX,startY,endX-startX,endY-startY); } function draw_ellipse(endX, endY) { ctx.clearRect(0,0,canvas.width,canvas.height); + ctx.strokeStyle = `${stroke_color}`; ctx.beginPath(); ctx.ellipse((startX+endX)/2, (endY+startY)/2,Math.abs((endX-startX)/2),Math.abs((endY-startY)/2),0,0,2*Math.PI); ctx.stroke(); } function draw_free(endX, endY) { + ctx.strokeStyle = `${stroke_color}`; ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(endX, endY); @@ -99,4 +149,9 @@ canvas.addEventListener("mouseup", (event) => { canvas.addEventListener("mouseleave", (event) => { is_drawing = false; }) + +document.getElementById("stroke_color").addEventListener("change", (event) => { + localStorage.setItem("stroke_color", event.currentTarget.value); + stroke_color = event.currentTarget.value; +}) //bug to be fixed later : drawing stops when pointer crosses toolbar \ No newline at end of file From 7f26d9d5624c6d58575d7b8ffd7a4e60750b357a Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 14:58:32 +0530 Subject: [PATCH 05/33] feat: User can choose linewidth and opacity. --- apollo.html | 2 +- canvas.js | 36 +++++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/apollo.html b/apollo.html index ac026b0..5978d4c 100644 --- a/apollo.html +++ b/apollo.html @@ -45,7 +45,7 @@
Stroke width:
- +
Opacity:
diff --git a/canvas.js b/canvas.js index e9dcaac..fa93b1e 100644 --- a/canvas.js +++ b/canvas.js @@ -8,30 +8,35 @@ let is_drawing = false; let startX = 0; let startY = 0; let curr_tool = localStorage.getItem("curr_tool"); -let stroke_color = localStorage.getItem("stroke_color"); let stroke_style = localStorage.getItem("stroke_style"); console.log(localStorage); - +if(!localStorage.getItem("stroke_width")){ + localStorage.setItem("stroke_width", "1"); +} +if(!localStorage.getItem("opacity")){ + localStorage.setItem("opacity","1"); +} if (!curr_tool) { localStorage.setItem("curr_tool", "selection"); curr_tool = "selection"; canvas.classList.remove("crosshair", "textcursor"); } -if (!stroke_color) { +if (!localStorage.getItem("stroke_color")) { if(localStorage.getItem("light") === 'false') localStorage.setItem("stroke_color", "#f5b811"); else { localStorage.setItem("stroke_color","#a60818"); } - stroke_color = localStorage.getItem("stroke_color"); } if (!stroke_style) { localStorage.setItem("stroke_style", "straight-line"); stroke_style = "straight-line"; } -document.getElementById("stroke_color").value = stroke_color; +document.getElementById("stroke_color").value = localStorage.getItem("stroke_color"); +document.getElementById("stroke_width").value = localStorage.getItem("stroke_width"); +document.getElementById("opacity").value = localStorage.getItem("opacity"); document.getElementById(curr_tool).classList.add("selected"); document.getElementById(stroke_style).classList.add("selected"); console.log(localStorage); @@ -59,8 +64,10 @@ function change_style(){ change_cursor(); change_style(); +ctx.lineWidth = JSON.parse(localStorage.getItem("stroke_width")); +ctx.globalAlpha = JSON.parse(localStorage.getItem("opacity")); +ctx.strokeStyle = localStorage.getItem("stroke_color"); - const tools = document.getElementsByClassName('tools'); for (const tool of tools) { tool.addEventListener("click", (event) => { @@ -86,10 +93,15 @@ for (const butt of stroke_buttons){ } )}; +document.getElementById("stroke_width").addEventListener("change", (event) => { + localStorage.setItem("stroke_width", JSON.stringify(event.target.value)); + ctx.lineWidth = event.target.value; + +}); + //drawing functions : function draw_line(endX,endY) { ctx.clearRect(0,0,canvas.width,canvas.height); - ctx.strokeStyle = `${stroke_color}`; ctx.beginPath(); ctx.moveTo(startX,startY); ctx.lineTo(endX,endY); @@ -97,20 +109,17 @@ function draw_line(endX,endY) { } function draw_rectangle(endX,endY) { ctx.clearRect(0,0,canvas.width,canvas.height); - ctx.strokeStyle = `${stroke_color}`; ctx.strokeRect(startX,startY,endX-startX,endY-startY); } function draw_ellipse(endX, endY) { ctx.clearRect(0,0,canvas.width,canvas.height); - ctx.strokeStyle = `${stroke_color}`; ctx.beginPath(); ctx.ellipse((startX+endX)/2, (endY+startY)/2,Math.abs((endX-startX)/2),Math.abs((endY-startY)/2),0,0,2*Math.PI); ctx.stroke(); } function draw_free(endX, endY) { - ctx.strokeStyle = `${stroke_color}`; ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(endX, endY); @@ -152,6 +161,11 @@ canvas.addEventListener("mouseleave", (event) => { document.getElementById("stroke_color").addEventListener("change", (event) => { localStorage.setItem("stroke_color", event.currentTarget.value); - stroke_color = event.currentTarget.value; + ctx.strokeStyle = event.currentTarget.value; +}) +document.getElementById("opacity").addEventListener("change", (event) => { + localStorage.setItem("opacity", event.currentTarget.value/100); + ctx.globalAlpha = event.currentTarget.value/100; }) + //bug to be fixed later : drawing stops when pointer crosses toolbar \ No newline at end of file From 047c78c49446b6062ce1f64b295fcbcd318a69d0 Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 18:54:19 +0530 Subject: [PATCH 06/33] bugfix: drawing a new item doesn't clear the canvas --- apollo.css | 9 ++++- apollo.html | 3 +- canvas.js | 110 ++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 108 insertions(+), 14 deletions(-) diff --git a/apollo.css b/apollo.css index 48a961c..4d969b5 100644 --- a/apollo.css +++ b/apollo.css @@ -123,8 +123,15 @@ button { stroke-width: 1.5; } -#paper { +#test { position: fixed; + background-color: transparent; + top:0; + left:0; +} +#final { + position: fixed; + background-color: transparent; top:0; left:0; } diff --git a/apollo.html b/apollo.html index 5978d4c..7e29bba 100644 --- a/apollo.html +++ b/apollo.html @@ -11,7 +11,8 @@ - Uhm sorry!....An error occured while loading the canvas. + Uhm sorry!....An error occured while loading the canvas. + Uhm sorry!....An error occured while loading the canvas. diff --git a/canvas.js b/canvas.js index fa93b1e..2b19a8f 100644 --- a/canvas.js +++ b/canvas.js @@ -1,16 +1,95 @@ -const canvas = document.getElementById('paper'); +const canvas = document.getElementById('test'); const ctx = canvas.getContext("2d"); +const canvas2 = document.getElementById('final'); +const ctx2 = canvas2.getContext("2d"); canvas.width = window.innerWidth; canvas.height = window.innerHeight; -/*add canvas resizing feature, store all canvas objects in an array*/ +canvas2.width = window.innerWidth; +canvas2.height = window.innerHeight; let is_drawing = false; let startX = 0; let startY = 0; +let pencil_array = []; let curr_tool = localStorage.getItem("curr_tool"); let stroke_style = localStorage.getItem("stroke_style"); console.log(localStorage); +//canvas2 drawing function +function canvas2draw(object) { + ctx.clearRect(0,0,canvas.width,canvas.height); + ctx2.strokeStyle = object.stroke_color; + ctx2.globalAlpha = JSON.parse(localStorage.getItem("opacity")); + ctx2.lineDashOffset = 0; + ctx2.lineWidth = object.stroke_width; + if(object.stroke_style === "dotted-line"){ + ctx2.setLineDash([2, 5]); + } + else{ + ctx2.setLineDash([]); + } + if(object.type === "line"){ + ctx2.beginPath(); + ctx2.moveTo(object.startX,object.startY); + ctx2.lineTo(object.endX,object.endY); + ctx2.stroke(); + } + else if(object.type === "circle"){ + ctx2.beginPath(); + ctx2.ellipse((object.startX+object.endX)/2, (object.endY+object.startY)/2,Math.abs((object.endX-object.startX)/2),Math.abs((object.endY-object.startY)/2),0,0,2*Math.PI); + ctx2.stroke(); + } + else if(object.type === "rectangle"){ + ctx2.strokeRect(object.startX,object.startY,object.endX-object.startX,object.endY-object.startY); + } + else if(object.type === "pencil"){ + for(let i = 1; i < pencil_array.length; i++){ + ctx2.beginPath(); + ctx2.moveTo(pencil_array[i-1].x, pencil_array[i-1].y); + ctx2.lineTo(pencil_array[i].x,pencil_array[i].y); + ctx2.stroke(); + } + } +} + +//Object constructors +function createObject(e){ + is_drawing = false; + let object = {}; + if(curr_tool === "line"){ + let obj = new Shape_obj(e.clientX,e.clientY); + object = obj; + object.type = "line"; + } + else if(curr_tool === "draw_circle"){ + let obj = new Shape_obj(e.clientX,e.clientY); + object = obj; + object.type = "circle"; + } + else if(curr_tool === "draw_rectangle"){ + let obj = new Shape_obj(e.clientX,e.clientY); + object = obj; + object.type = "rectangle"; + } + else if(curr_tool === "pencil"){ + object.type = "pencil"; + } + object.stroke_color = document.getElementById("stroke_color").value; + object.opacity = document.getElementById("opacity").value/100; + object.stroke_width = document.getElementById("stroke_width").value; + object.stroke_style = stroke_style; + canvas2draw(object); +} +function Shape_obj(endX,endY){ + this.startX = startX; + this.startY = startY; + this.endX = endX; + this.endY= endY; +} + + + + if(!localStorage.getItem("stroke_width")){ localStorage.setItem("stroke_width", "1"); } @@ -36,7 +115,7 @@ if (!stroke_style) { document.getElementById("stroke_color").value = localStorage.getItem("stroke_color"); document.getElementById("stroke_width").value = localStorage.getItem("stroke_width"); -document.getElementById("opacity").value = localStorage.getItem("opacity"); +document.getElementById("opacity").value = localStorage.getItem("opacity")*100; document.getElementById(curr_tool).classList.add("selected"); document.getElementById(stroke_style).classList.add("selected"); console.log(localStorage); @@ -78,7 +157,7 @@ for (const tool of tools) { document.getElementById(curr_tool).classList.add("selected"); console.log(`class selected added to ${curr_tool}`); change_cursor(); - })}; + })} const stroke_buttons = document.getElementsByClassName('stroke_button'); for (const butt of stroke_buttons){ @@ -132,6 +211,9 @@ canvas.addEventListener("mousedown", (event) => { is_drawing = true; startX = event.clientX; startY = event.clientY; + if(curr_tool === "pencil"){ + pencil_array = [{x: startX, y: startY}]; + } }) canvas.addEventListener("mousemove", (event) => { if (is_drawing === true) { @@ -145,19 +227,20 @@ canvas.addEventListener("mousemove", (event) => { draw_ellipse(event.clientX, event.clientY); } else if(curr_tool === "pencil"){ + pencil_array.push({x: event.clientX, y: event.clientY}); draw_free(event.clientX, event.clientY); } } }) -canvas.addEventListener("mouseup", (event) => { - is_drawing = false; -}) +canvas.addEventListener("mouseup", createObject); //to prevent glitches when mouse leaves canvas -canvas.addEventListener("mouseleave", (event) => { - is_drawing = false; -}) +canvas.addEventListener("mouseleave", (e) => { + if(is_drawing === true){ + createObject(e); + } + }) document.getElementById("stroke_color").addEventListener("change", (event) => { localStorage.setItem("stroke_color", event.currentTarget.value); @@ -167,5 +250,8 @@ document.getElementById("opacity").addEventListener("change", (event) => { localStorage.setItem("opacity", event.currentTarget.value/100); ctx.globalAlpha = event.currentTarget.value/100; }) - -//bug to be fixed later : drawing stops when pointer crosses toolbar \ No newline at end of file +//TODO: +//bug to be fixed later : drawing stops when pointer crosses toolbar +//bug to be fixed later : stroke dotted appears strange with higher opacities +//later change: change the eraser's crosshair +//add canvas resizing feature, store all canvas objects in an array \ No newline at end of file From e720539bcee45af17f82120f899bbd5ca6d915b2 Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 19:20:22 +0530 Subject: [PATCH 07/33] bugfix: content not lost on site refresh --- canvas.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/canvas.js b/canvas.js index 2b19a8f..cf93bc7 100644 --- a/canvas.js +++ b/canvas.js @@ -19,7 +19,7 @@ console.log(localStorage); function canvas2draw(object) { ctx.clearRect(0,0,canvas.width,canvas.height); ctx2.strokeStyle = object.stroke_color; - ctx2.globalAlpha = JSON.parse(localStorage.getItem("opacity")); + ctx2.globalAlpha = object.opacity; ctx2.lineDashOffset = 0; ctx2.lineWidth = object.stroke_width; if(object.stroke_style === "dotted-line"){ @@ -43,10 +43,10 @@ function canvas2draw(object) { ctx2.strokeRect(object.startX,object.startY,object.endX-object.startX,object.endY-object.startY); } else if(object.type === "pencil"){ - for(let i = 1; i < pencil_array.length; i++){ + for(let i = 1; i < object.pencil_array.length; i++){ ctx2.beginPath(); - ctx2.moveTo(pencil_array[i-1].x, pencil_array[i-1].y); - ctx2.lineTo(pencil_array[i].x,pencil_array[i].y); + ctx2.moveTo(object.pencil_array[i-1].x, object.pencil_array[i-1].y); + ctx2.lineTo(object.pencil_array[i].x,object.pencil_array[i].y); ctx2.stroke(); } } @@ -73,12 +73,16 @@ function createObject(e){ } else if(curr_tool === "pencil"){ object.type = "pencil"; + object.pencil_array = pencil_array; } object.stroke_color = document.getElementById("stroke_color").value; object.opacity = document.getElementById("opacity").value/100; object.stroke_width = document.getElementById("stroke_width").value; object.stroke_style = stroke_style; canvas2draw(object); + let canvas_array = JSON.parse(localStorage.getItem("canvas_array")); + canvas_array.push(object); + localStorage.setItem("canvas_array", JSON.stringify(canvas_array)); } function Shape_obj(endX,endY){ this.startX = startX; @@ -88,7 +92,9 @@ function Shape_obj(endX,endY){ } - +if(!localStorage.getItem("canvas_array")){ + localStorage.setItem("canvas_array", "[]"); +} if(!localStorage.getItem("stroke_width")){ localStorage.setItem("stroke_width", "1"); @@ -146,6 +152,11 @@ change_style(); ctx.lineWidth = JSON.parse(localStorage.getItem("stroke_width")); ctx.globalAlpha = JSON.parse(localStorage.getItem("opacity")); ctx.strokeStyle = localStorage.getItem("stroke_color"); +const canvas_array = JSON.parse(localStorage.getItem("canvas_array")); +for(let item of canvas_array){ + console.log(item); + canvas2draw(item); +} const tools = document.getElementsByClassName('tools'); for (const tool of tools) { From 4acc63ce7368dffd20be864e5fbd1056a074fac0 Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 20:33:21 +0530 Subject: [PATCH 08/33] feature: User can undo past changes both using Ctrl+Z and the onscreen undo button. --- canvas.js | 96 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/canvas.js b/canvas.js index cf93bc7..040ef95 100644 --- a/canvas.js +++ b/canvas.js @@ -13,7 +13,7 @@ let startY = 0; let pencil_array = []; let curr_tool = localStorage.getItem("curr_tool"); let stroke_style = localStorage.getItem("stroke_style"); -console.log(localStorage); +console.log(localStorage.getItem("undo_stack")); //canvas2 drawing function function canvas2draw(object) { @@ -51,7 +51,11 @@ function canvas2draw(object) { } } } - +function rerender(){ + const undo_stack = JSON.parse(localStorage.getItem("undo_stack")); + for(let item of undo_stack){ + canvas2draw(item); +}} //Object constructors function createObject(e){ is_drawing = false; @@ -80,9 +84,9 @@ function createObject(e){ object.stroke_width = document.getElementById("stroke_width").value; object.stroke_style = stroke_style; canvas2draw(object); - let canvas_array = JSON.parse(localStorage.getItem("canvas_array")); - canvas_array.push(object); - localStorage.setItem("canvas_array", JSON.stringify(canvas_array)); + let undo_stack = JSON.parse(localStorage.getItem("undo_stack")); + undo_stack.push(object); + localStorage.setItem("undo_stack", JSON.stringify(undo_stack)); } function Shape_obj(endX,endY){ this.startX = startX; @@ -90,10 +94,31 @@ function Shape_obj(endX,endY){ this.endX = endX; this.endY= endY; } +//function to change the cursor +function change_cursor(){ + canvas.classList.remove("crosshair", "textcursor"); + if (curr_tool === "text") { + canvas.classList.add("textcursor"); + } + else if (curr_tool !== "selection") { + canvas.classList.add("crosshair"); + } +} +//function to change the stroke style +function change_style(){ + if(stroke_style === "dotted-line"){ + ctx.setLineDash([2, 5]); + } + else{ + ctx.setLineDash([]); + } + ctx.lineDashOffset = 0; +} -if(!localStorage.getItem("canvas_array")){ - localStorage.setItem("canvas_array", "[]"); +if(!localStorage.getItem("undo_stack")){ + localStorage.setItem("undo_stack", "[]"); + console.log("changed",localStorage.getItem("undo_stack")); } if(!localStorage.getItem("stroke_width")){ @@ -124,49 +149,24 @@ document.getElementById("stroke_width").value = localStorage.getItem("stroke_wid document.getElementById("opacity").value = localStorage.getItem("opacity")*100; document.getElementById(curr_tool).classList.add("selected"); document.getElementById(stroke_style).classList.add("selected"); -console.log(localStorage); -//function to change the cursor -function change_cursor(){ - canvas.classList.remove("crosshair", "textcursor"); - if (curr_tool === "text") { - canvas.classList.add("textcursor"); - } - else if (curr_tool !== "selection") { - canvas.classList.add("crosshair"); - } -} -//function to change the stroke style -function change_style(){ - if(stroke_style === "dotted-line"){ - ctx.setLineDash([2, 5]); - } - else{ - ctx.setLineDash([]); - } - ctx.lineDashOffset = 0; -} + + change_cursor(); change_style(); ctx.lineWidth = JSON.parse(localStorage.getItem("stroke_width")); ctx.globalAlpha = JSON.parse(localStorage.getItem("opacity")); ctx.strokeStyle = localStorage.getItem("stroke_color"); -const canvas_array = JSON.parse(localStorage.getItem("canvas_array")); -for(let item of canvas_array){ - console.log(item); - canvas2draw(item); -} +rerender(); const tools = document.getElementsByClassName('tools'); for (const tool of tools) { tool.addEventListener("click", (event) => { document.getElementById(curr_tool).classList.remove("selected"); - console.log(`class selected removed from ${curr_tool}.`); localStorage.setItem("curr_tool", event.currentTarget.id); curr_tool = localStorage.getItem("curr_tool"); document.getElementById(curr_tool).classList.add("selected"); - console.log(`class selected added to ${curr_tool}`); change_cursor(); })} @@ -174,11 +174,9 @@ const stroke_buttons = document.getElementsByClassName('stroke_button'); for (const butt of stroke_buttons){ butt.addEventListener("click", (event) => { document.getElementById(stroke_style).classList.remove("selected"); - console.log(`class selected removed from ${stroke_style}.`); localStorage.setItem("stroke_style", event.currentTarget.id); stroke_style = localStorage.getItem("stroke_style"); document.getElementById(stroke_style).classList.add("selected"); - console.log(`class selected added to ${stroke_style}`); change_style(); } )}; @@ -261,6 +259,30 @@ document.getElementById("opacity").addEventListener("change", (event) => { localStorage.setItem("opacity", event.currentTarget.value/100); ctx.globalAlpha = event.currentTarget.value/100; }) + + + +/*-------------------------------------------------------------------------------------------------------*/ +//Undo redo logic: + +document.getElementById("undo").addEventListener("click", (e) => +{ + let arr = JSON.parse(localStorage.getItem("undo_stack")); + arr.pop(); + localStorage.setItem("undo_stack", JSON.stringify(arr)); + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + rerender(); +}); +document.addEventListener('keydown', function(event) { + if (event.ctrlKey && event.key === 'z') { + let arr = JSON.parse(localStorage.getItem("undo_stack")); + arr.pop(); + localStorage.setItem("undo_stack", JSON.stringify(arr)); + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + rerender(); + } +} +); //TODO: //bug to be fixed later : drawing stops when pointer crosses toolbar //bug to be fixed later : stroke dotted appears strange with higher opacities From 55f0eb0d1770484397df25e43e57941ff121d5cd Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 22:11:43 +0530 Subject: [PATCH 09/33] UI improvement: color scheme of light mode --- apollo.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apollo.css b/apollo.css index 4d969b5..7161d73 100644 --- a/apollo.css +++ b/apollo.css @@ -7,11 +7,11 @@ } .light { - --bg-color: #D6A99D ; - --button-background-color: #FBF3D5; + --bg-color: #cbcbcb ; + --button-background-color:#ffffe3 ; --button-highlight-color: black; - --button-fill-color: black; - --button-active-color: #1b8b98; + --button-fill-color: #4a4a4a; + --button-active-color:#6d8196 ; } * { box-sizing: border-box; From c3a7207bc212da3c9e4b3b081cd658701e042b91 Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 23:09:32 +0530 Subject: [PATCH 10/33] feature: redo functionality --- apollo.css | 46 ++++++++++++++++++++++++++++++++---------- apollo.html | 8 ++++++-- canvas.js | 58 ++++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 83 insertions(+), 29 deletions(-) diff --git a/apollo.css b/apollo.css index 7161d73..57f1908 100644 --- a/apollo.css +++ b/apollo.css @@ -93,19 +93,46 @@ button { background-color: var(--button-background-color); z-index:1; } - -#undo { - left:20px; +.extra_functionality:hover { + outline:none; + box-shadow: none; +} +.extra_functionality:hover svg { + stroke: var(--button-highlight-color); + stroke-width: 1.5; +} +.history { + position: fixed; + top:0.6875rem; + left:1.25rem; + height: 5vh; + aspect-ratio: 2.5/1; + display: flex; + justify-content: center; + align-items: center; + border-radius: 1rem; + padding:0; + margin:0; + background-color: var(--button-background-color); + z-index:1; +} +.history button svg{ + height:60%; + width:100%; fill:none; stroke: var(--button-fill-color); } -#toggle_theme { - right:20px; -} -.extra_functionality:hover { +.history button:hover { outline:none; box-shadow: none; } +.history button:hover svg { + stroke: var(--button-highlight-color); + stroke-width: 1.5; +} +#toggle_theme { + right:20px; +} #toggle_theme svg:last-child { @@ -118,10 +145,7 @@ button { display: none; } -.extra_functionality:hover svg { - stroke: var(--button-highlight-color); - stroke-width: 1.5; -} + #test { position: fixed; diff --git a/apollo.html b/apollo.html index 7e29bba..9c3943a 100644 --- a/apollo.html +++ b/apollo.html @@ -32,10 +32,14 @@ - - + +
Stroke color:
diff --git a/canvas.js b/canvas.js index 040ef95..98d7172 100644 --- a/canvas.js +++ b/canvas.js @@ -11,6 +11,7 @@ let is_drawing = false; let startX = 0; let startY = 0; let pencil_array = []; +let prev_undo = false; let curr_tool = localStorage.getItem("curr_tool"); let stroke_style = localStorage.getItem("stroke_style"); console.log(localStorage.getItem("undo_stack")); @@ -118,7 +119,10 @@ function change_style(){ if(!localStorage.getItem("undo_stack")){ localStorage.setItem("undo_stack", "[]"); - console.log("changed",localStorage.getItem("undo_stack")); +} + +if(!localStorage.getItem("redo_stack")){ + localStorage.setItem("redo_stack", "[]"); } if(!localStorage.getItem("stroke_width")){ @@ -226,6 +230,8 @@ canvas.addEventListener("mousedown", (event) => { }) canvas.addEventListener("mousemove", (event) => { if (is_drawing === true) { + prev_undo = false; + localStorage.setItem("redo_stack","[]"); if(curr_tool === "line"){ draw_line(event.clientX, event.clientY); } @@ -265,24 +271,44 @@ document.getElementById("opacity").addEventListener("change", (event) => { /*-------------------------------------------------------------------------------------------------------*/ //Undo redo logic: -document.getElementById("undo").addEventListener("click", (e) => -{ +function undo(e){ + prev_undo = true; let arr = JSON.parse(localStorage.getItem("undo_stack")); - arr.pop(); - localStorage.setItem("undo_stack", JSON.stringify(arr)); - ctx2.clearRect(0,0,canvas2.width,canvas2.height); - rerender(); -}); -document.addEventListener('keydown', function(event) { - if (event.ctrlKey && event.key === 'z') { + let arr2 = JSON.parse(localStorage.getItem("redo_stack")); + if(arr.length !== 0) + { + const obj = arr.pop(); + arr2.push(obj); + localStorage.setItem("undo_stack", JSON.stringify(arr)); + localStorage.setItem("redo_stack", JSON.stringify(arr2)); + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + rerender(); + } +} + +function redo(e){ let arr = JSON.parse(localStorage.getItem("undo_stack")); - arr.pop(); - localStorage.setItem("undo_stack", JSON.stringify(arr)); - ctx2.clearRect(0,0,canvas2.width,canvas2.height); - rerender(); - } + let arr2 = JSON.parse(localStorage.getItem("redo_stack")); + if(arr2.length !== 0){ + const obj = arr2.pop(); + arr.push(obj); + localStorage.setItem("undo_stack", JSON.stringify(arr)); + localStorage.setItem("redo_stack", JSON.stringify(arr2)); + canvas2draw(obj); + } } -); +document.getElementById("undo").addEventListener("click", undo); +document.addEventListener('keydown', (event) => { + if (event.ctrlKey && event.key === 'z'){ + undo(); +}}) + +document.getElementById("redo").addEventListener("click", redo); +document.addEventListener('keydown', (event) => { + if (event.ctrlKey && event.key === 'y'){ + redo(); +}}) + //TODO: //bug to be fixed later : drawing stops when pointer crosses toolbar //bug to be fixed later : stroke dotted appears strange with higher opacities From c1d0086e28be617c806b5ad9f1c73ccae2b474fa Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 24 Mar 2026 23:55:13 +0530 Subject: [PATCH 11/33] Rename apollo.html to index.html --- apollo.html => index.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apollo.html => index.html (100%) diff --git a/apollo.html b/index.html similarity index 100% rename from apollo.html rename to index.html From 613547941607d9e32a9bbe80369404b6dfe93178 Mon Sep 17 00:00:00 2001 From: xylo Date: Thu, 26 Mar 2026 00:47:31 +0530 Subject: [PATCH 12/33] Feat: Users can draw polygons and other shapes with straight lines as edges. Polygon starts drawing when a click is registerd, clicking on the canvas adds vertices to the polygon and double-clicking finishes the polygon automatically by adding the final edge. --- apollo.css | 1 + canvas.js | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++--- index.html | 2 +- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/apollo.css b/apollo.css index 57f1908..1a1b649 100644 --- a/apollo.css +++ b/apollo.css @@ -209,3 +209,4 @@ button { width:100%; fill:none; } + diff --git a/canvas.js b/canvas.js index 98d7172..29da28f 100644 --- a/canvas.js +++ b/canvas.js @@ -11,11 +11,15 @@ let is_drawing = false; let startX = 0; let startY = 0; let pencil_array = []; +let in_poly_mode = false; let prev_undo = false; let curr_tool = localStorage.getItem("curr_tool"); let stroke_style = localStorage.getItem("stroke_style"); console.log(localStorage.getItem("undo_stack")); +//----------------------------------------------------------------------------------------- +//main funcs + //canvas2 drawing function function canvas2draw(object) { ctx.clearRect(0,0,canvas.width,canvas.height); @@ -51,6 +55,20 @@ function canvas2draw(object) { ctx2.stroke(); } } + else if(object.type === "polygon"){ + for(let i = 1; i < object.pencil_array.length; i++){ + ctx2.beginPath(); + ctx2.moveTo(object.pencil_array[i-1].x, object.pencil_array[i-1].y); + ctx2.lineTo(object.pencil_array[i].x,object.pencil_array[i].y); + ctx2.stroke(); + } + for(let i = 0; i < 2 ; i++){ + ctx2.beginPath(); + ctx2.moveTo(object.pencil_array[object.pencil_array.length-1].x, object.pencil_array[object.pencil_array.length-1].y); + ctx2.lineTo(object.pencil_array[0].x,object.pencil_array[0].y); + ctx2.stroke(); + } + } } function rerender(){ const undo_stack = JSON.parse(localStorage.getItem("undo_stack")); @@ -80,14 +98,18 @@ function createObject(e){ object.type = "pencil"; object.pencil_array = pencil_array; } + else if(curr_tool === "polygon"){ + object.type = "polygon"; + object.pencil_array = pencil_array; + } object.stroke_color = document.getElementById("stroke_color").value; object.opacity = document.getElementById("opacity").value/100; object.stroke_width = document.getElementById("stroke_width").value; object.stroke_style = stroke_style; - canvas2draw(object); let undo_stack = JSON.parse(localStorage.getItem("undo_stack")); undo_stack.push(object); localStorage.setItem("undo_stack", JSON.stringify(undo_stack)); + canvas2draw(object); } function Shape_obj(endX,endY){ this.startX = startX; @@ -109,11 +131,14 @@ function change_cursor(){ function change_style(){ if(stroke_style === "dotted-line"){ ctx.setLineDash([2, 5]); + ctx2.setLineDash([2, 5]); } else{ ctx.setLineDash([]); + ctx2.setLineDash([]); } ctx.lineDashOffset = 0; + ctx2.lineDashOffset = 0; } @@ -188,10 +213,11 @@ for (const butt of stroke_buttons){ document.getElementById("stroke_width").addEventListener("change", (event) => { localStorage.setItem("stroke_width", JSON.stringify(event.target.value)); ctx.lineWidth = event.target.value; + ctx2.lineWidth = event.target.value; }); - -//drawing functions : +//---------------------------------------------------------------------------------------- +//live drawing functions : function draw_line(endX,endY) { ctx.clearRect(0,0,canvas.width,canvas.height); ctx.beginPath(); @@ -220,6 +246,8 @@ function draw_free(endX, endY) { startY = endY; } +//----------------------------------------------------------------------------------------- +//event listeners canvas.addEventListener("mousedown", (event) => { is_drawing = true; startX = event.clientX; @@ -228,6 +256,39 @@ canvas.addEventListener("mousedown", (event) => { pencil_array = [{x: startX, y: startY}]; } }) +canvas.addEventListener("click", (event) => { + if(curr_tool === "polygon"){ + is_drawing = true; + startX = event.clientX; + startY = event.clientY; + if(in_poly_mode === false){ + in_poly_mode = true; + pencil_array = [{x: startX, y: startY}]; + } + else{ + ctx.clearRect(0,0,canvas.width,canvas.height); + pencil_array.push({x: startX, y: startY}); + ctx2.beginPath(); + ctx2.moveTo(startX,startY); + ctx2.lineTo(pencil_array[pencil_array.length-2].x,pencil_array[pencil_array.length-2].y); + ctx2.stroke(); + } + } +}) +canvas.addEventListener("dblclick", (e) => { + if(curr_tool === "polygon"){ + is_drawing = true; + startX = e.clientX; + startY = e.clientY; + in_poly_mode = false; + pencil_array.push({x: startX, y: startY}); + ctx.beginPath(); + ctx.moveTo(startX,startY); + ctx.lineTo(pencil_array[0].x,pencil_array[0].y); + ctx.stroke(); + createObject(e) + } +}) canvas.addEventListener("mousemove", (event) => { if (is_drawing === true) { prev_undo = false; @@ -245,10 +306,17 @@ canvas.addEventListener("mousemove", (event) => { pencil_array.push({x: event.clientX, y: event.clientY}); draw_free(event.clientX, event.clientY); } + else if(curr_tool === "polygon"){ + draw_line(event.clientX, event.clientY); + } } }) -canvas.addEventListener("mouseup", createObject); +canvas.addEventListener("mouseup", (e) => { + if(curr_tool !== "polygon"){ + createObject(e); + }} +); //to prevent glitches when mouse leaves canvas canvas.addEventListener("mouseleave", (e) => { @@ -260,10 +328,12 @@ canvas.addEventListener("mouseleave", (e) => { document.getElementById("stroke_color").addEventListener("change", (event) => { localStorage.setItem("stroke_color", event.currentTarget.value); ctx.strokeStyle = event.currentTarget.value; + ctx2.strokeStyle = event.currentTarget.value; }) document.getElementById("opacity").addEventListener("change", (event) => { localStorage.setItem("opacity", event.currentTarget.value/100); ctx.globalAlpha = event.currentTarget.value/100; + ctx2.globalAlpha = event.currentTarget.value/100; }) diff --git a/index.html b/index.html index 9c3943a..01edbfa 100644 --- a/index.html +++ b/index.html @@ -15,10 +15,10 @@ Uhm sorry!....An error occured while loading the canvas. - + From 04cea1e05e24d11c80c7057cc0f68762e20f6413 Mon Sep 17 00:00:00 2001 From: xylo Date: Thu, 26 Mar 2026 19:15:31 +0530 Subject: [PATCH 13/33] Feat : Added toolbar for text tool --- apollo.css | 16 +++++++-------- canvas.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++------- index.html | 16 ++++++++++++++- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/apollo.css b/apollo.css index 1a1b649..6c910af 100644 --- a/apollo.css +++ b/apollo.css @@ -160,7 +160,7 @@ button { left:0; } -#stroke_manipulation{ +.beautifiers{ border:0; margin:0; padding:1rem 1rem 1rem 1rem; @@ -177,33 +177,33 @@ button { gap: 0.7rem; left:0rem; } -#stroke_manipulation pre{ +.beautifiers pre{ margin:0; font-size:1rem; color: var(--button-fill-color); } -#stroke_manipulation input{ +.beautifiers input{ padding: 0; margin:0; border : none; } -#stroke_manipulation input:hover{ +.beautifiers input:hover{ cursor: pointer; } -#stroke_manipulation div{ +.beautifiers div{ display: inline-flex; align-items: center; justify-content: center; gap:1rem; } -#stroke_manipulation div button{ +.beautifiers div button{ width:3rem; } -#stroke_manipulation div button:hover svg{ +.beautifiers div button:hover svg{ stroke: var(--button-highlight-color); stroke-width: 2.70; } -#stroke_manipulation div button svg{ +.beautifiers div button svg{ stroke: var(--button-fill-color); height: 80%; width:100%; diff --git a/canvas.js b/canvas.js index 29da28f..1eb3376 100644 --- a/canvas.js +++ b/canvas.js @@ -117,6 +117,23 @@ function Shape_obj(endX,endY){ this.endX = endX; this.endY= endY; } + +//function to switch between font toolbar and shapes toolbar +function change_toolbar(s){ + if(s === "font"){ + document.getElementById("font_manipulation").style.display = "flex"; + document.getElementById("stroke_manipulation").style.display = "none"; + } + else if(s === "stroke"){ + document.getElementById("font_manipulation").style.display = "none"; + document.getElementById("stroke_manipulation").style.display = "flex"; + } + else if(s === "selection"){ + document.getElementById("font_manipulation").style.display = "none"; + document.getElementById("stroke_manipulation").style.display = "none"; + } +} + //function to change the cursor function change_cursor(){ canvas.classList.remove("crosshair", "textcursor"); @@ -127,6 +144,7 @@ function change_cursor(){ canvas.classList.add("crosshair"); } } + //function to change the stroke style function change_style(){ if(stroke_style === "dotted-line"){ @@ -140,8 +158,17 @@ function change_style(){ ctx.lineDashOffset = 0; ctx2.lineDashOffset = 0; } - - +//------------------------------Initializers----------------------------------------------- +if(!localStorage.getItem("font_color")){ + localStorage.setItem("font_color","#226e08"); +} +if(!localStorage.getItem("font_family")){ + localStorage.setItem("font_family","Arial"); +} +if(!localStorage.getItem("toolbar")){ + localStorage.setItem("toolbar","selection"); + change_toolbar("selection"); +} if(!localStorage.getItem("undo_stack")){ localStorage.setItem("undo_stack", "[]"); } @@ -173,25 +200,42 @@ if (!stroke_style) { stroke_style = "straight-line"; } +document.getElementById("font_color").value = localStorage.getItem("font_color"); +document.getElementById("font_family").value = localStorage.getItem("font_family"); document.getElementById("stroke_color").value = localStorage.getItem("stroke_color"); document.getElementById("stroke_width").value = localStorage.getItem("stroke_width"); document.getElementById("opacity").value = localStorage.getItem("opacity")*100; document.getElementById(curr_tool).classList.add("selected"); document.getElementById(stroke_style).classList.add("selected"); - - - +change_toolbar(localStorage.getItem("toolbar")) change_cursor(); change_style(); ctx.lineWidth = JSON.parse(localStorage.getItem("stroke_width")); ctx.globalAlpha = JSON.parse(localStorage.getItem("opacity")); ctx.strokeStyle = localStorage.getItem("stroke_color"); +change_style(); +ctx2.lineWidth = JSON.parse(localStorage.getItem("stroke_width")); +ctx2.globalAlpha = JSON.parse(localStorage.getItem("opacity")); +ctx2.strokeStyle = localStorage.getItem("stroke_color"); rerender(); const tools = document.getElementsByClassName('tools'); for (const tool of tools) { tool.addEventListener("click", (event) => { + if(tool.id === "text"){ + change_toolbar("font"); + localStorage.setItem("toolbar", "font") + } + else if(tool.id === "selection"){ + change_toolbar("selection"); + localStorage.setItem("toolbar","selection"); + } + else + { + change_toolbar("stroke"); + localStorage.setItem("toolbar","stroke"); + } document.getElementById(curr_tool).classList.remove("selected"); localStorage.setItem("curr_tool", event.currentTarget.id); curr_tool = localStorage.getItem("curr_tool"); @@ -209,7 +253,6 @@ for (const butt of stroke_buttons){ change_style(); } )}; - document.getElementById("stroke_width").addEventListener("change", (event) => { localStorage.setItem("stroke_width", JSON.stringify(event.target.value)); ctx.lineWidth = event.target.value; @@ -277,7 +320,6 @@ canvas.addEventListener("click", (event) => { }) canvas.addEventListener("dblclick", (e) => { if(curr_tool === "polygon"){ - is_drawing = true; startX = e.clientX; startY = e.clientY; in_poly_mode = false; @@ -320,6 +362,7 @@ canvas.addEventListener("mouseup", (e) => { //to prevent glitches when mouse leaves canvas canvas.addEventListener("mouseleave", (e) => { + in_poly_mode = false; if(is_drawing === true){ createObject(e); } diff --git a/index.html b/index.html index 01edbfa..05e7011 100644 --- a/index.html +++ b/index.html @@ -40,7 +40,7 @@ - +
Stroke color:
@@ -54,6 +54,20 @@
Opacity:
+ +
Text color:
+ +
Font family:
+ +
From 5dbfc6ed9deeffd00091df2970202d75956f59d6 Mon Sep 17 00:00:00 2001 From: xylo Date: Fri, 27 Mar 2026 02:44:10 +0530 Subject: [PATCH 14/33] Feat : Slider value display and text box --- apollo.css | 3 ++ canvas.js | 89 ++++++++++++++++++++++++++++++++++++++++++++++++- index.html | 6 ++-- toggle_theme.js | 14 ++++++-- 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/apollo.css b/apollo.css index 6c910af..eb29e13 100644 --- a/apollo.css +++ b/apollo.css @@ -209,4 +209,7 @@ button { width:100%; fill:none; } +output{ + color: var(--button-fill-color); +} diff --git a/canvas.js b/canvas.js index 1eb3376..7711544 100644 --- a/canvas.js +++ b/canvas.js @@ -15,6 +15,8 @@ let in_poly_mode = false; let prev_undo = false; let curr_tool = localStorage.getItem("curr_tool"); let stroke_style = localStorage.getItem("stroke_style"); +let text_box = 0; +let mouse_downed_text = 0; console.log(localStorage.getItem("undo_stack")); //----------------------------------------------------------------------------------------- @@ -158,10 +160,39 @@ function change_style(){ ctx.lineDashOffset = 0; ctx2.lineDashOffset = 0; } + +function add_element(s,startX,startY,endX,endY){ + if(s === "text"){ + const input = document.createElement("textarea"); + input.style.position = "absolute"; + input.id = "textbox"; + input.style.resize = "none"; + input.style.top = `${Math.min(startY,endY)}px`; + input.style.left = `${Math.min(startX,endX)}px`; + input.style.color = `${localStorage.getItem("font_color")}`; + input.style.fontFamily = `${localStorage.getItem("font_family")}`; + input.style.backgroundColor = "transparent"; + input.style.overflow = "none"; + input.style.height = `${Math.abs(startY-endY)}px`; + input.style.width = `${Math.abs(startX-endX)}px`; + document.body.appendChild(input); + input.addEventListener("input",(e)=>{ + input.style.height = `${Math.abs(startY-endY)}px`; + input.style.height = `${input.scrollHeight}px`; + }) + input.focus(); + input.addEventListener("mousedown",(e)=>{e.stopPropagation()},true); + } +} + + //------------------------------Initializers----------------------------------------------- if(!localStorage.getItem("font_color")){ localStorage.setItem("font_color","#226e08"); } +if(!localStorage.getItem("font_size")){ + localStorage.setItem("font_size","1rem"); +} if(!localStorage.getItem("font_family")){ localStorage.setItem("font_family","Arial"); } @@ -259,6 +290,12 @@ document.getElementById("stroke_width").addEventListener("change", (event) => { ctx2.lineWidth = event.target.value; }); +document.getElementById("font_color").addEventListener("change", (e) => { + localStorage.setItem("font_color",e.currentTarget.value); +}); +document.getElementById("font_family").addEventListener("change", (e) => { + localStorage.setItem("font_family", e.currentTarget.value); +}); //---------------------------------------------------------------------------------------- //live drawing functions : function draw_line(endX,endY) { @@ -298,7 +335,24 @@ canvas.addEventListener("mousedown", (event) => { if(curr_tool === "pencil"){ pencil_array = [{x: startX, y: startY}]; } + else if(curr_tool === "text"){ + if(text_box === 0){ + mouse_downed_text = 1; + ctx.lineWidth = 1; + if(localStorage.getItem('light') === 'true'){ + ctx.strokeStyle = "black"; + } + else{ + ctx.strokeStyle = "white"; + } + } + } }) +document.body.addEventListener("mousedown" , (e) => { + if(curr_tool === "text" && text_box === 1){ + //create ctx2 text + } +},false) canvas.addEventListener("click", (event) => { if(curr_tool === "polygon"){ is_drawing = true; @@ -351,13 +405,30 @@ canvas.addEventListener("mousemove", (event) => { else if(curr_tool === "polygon"){ draw_line(event.clientX, event.clientY); } + else if(curr_tool === "text"){ + if(text_box === 0 && mouse_downed_text === 1){ + draw_rectangle(event.clientX, event.clientY); + } + } } }) canvas.addEventListener("mouseup", (e) => { - if(curr_tool !== "polygon"){ + if(curr_tool === "text" && text_box === 0){ + ctx.lineWidth = localStorage.getItem("stroke_width"); + ctx.strokeStyle = localStorage.getItem("stroke_color"); + add_element("text",startX,startY,e.clientX,e.clientY); + is_drawing = false; + text_box = 1; + mouse_downed_text = 0; + } + else if(curr_tool === "text" && text_box === 1){ + text_box = 0; + } + else if(curr_tool !== "polygon"){ createObject(e); }} + ); //to prevent glitches when mouse leaves canvas @@ -422,6 +493,22 @@ document.addEventListener('keydown', (event) => { redo(); }}) +//----------------------------------------------------------------------------------------- +//Interactive sliders: +document.getElementById("font_size_value").textContent = `${parseFloat(getComputedStyle(document.documentElement).fontSize)*document.getElementById("font_size").value}px`; +document.getElementById("font_size").addEventListener("input", (event) => { +document.getElementById("font_size_value").textContent = `${parseFloat(getComputedStyle(document.documentElement).fontSize)*event.currentTarget.value}px`; +}); + +document.getElementById("opacity_value").textContent = `${document.getElementById("opacity").value/100}`; +document.getElementById("opacity").addEventListener("input", (event) => { +document.getElementById("opacity_value").textContent = `${event.currentTarget.value/100}`; +}); + +document.getElementById("stroke_width_value").textContent = `${document.getElementById("stroke_width").value}px`; +document.getElementById("stroke_width").addEventListener("input", (event) => { +document.getElementById("stroke_width_value").textContent = `${event.currentTarget.value}px`; +}); //TODO: //bug to be fixed later : drawing stops when pointer crosses toolbar //bug to be fixed later : stroke dotted appears strange with higher opacities diff --git a/index.html b/index.html index 05e7011..55cb411 100644 --- a/index.html +++ b/index.html @@ -50,9 +50,9 @@
Stroke width:
- +
Opacity:
- +
Text color:
@@ -67,6 +67,8 @@ +
Font size:
+
diff --git a/toggle_theme.js b/toggle_theme.js index 585457d..0b0c708 100644 --- a/toggle_theme.js +++ b/toggle_theme.js @@ -2,12 +2,22 @@ let theme = localStorage.getItem('light') const themeSwitch = document.getElementById('toggle_theme') const enableLightmode = () => { - document.body.classList.add('light') + document.documentElement.style.setProperty('--bg-color', '#cbcbcb'); + document.documentElement.style.setProperty('--button-background-color', '#ffffe3'); + document.documentElement.style.setProperty('--button-highlight-color', 'black'); + document.documentElement.style.setProperty('--button-fill-color', '#4a4a4a'); + document.documentElement.style.setProperty('--button-active-color', '#6d8196'); + document.body.classList.add("light"); localStorage.setItem('light', 'true') } const disableLightmode = () => { - document.body.classList.remove('light') + document.documentElement.style.setProperty('--bg-color', '#222831'); + document.documentElement.style.setProperty('--button-background-color', '#393E46'); + document.documentElement.style.setProperty('--button-highlight-color', '#DFD0B8'); + document.documentElement.style.setProperty('--button-fill-color', 'white'); + document.documentElement.style.setProperty('--button-active-color', 'purple'); + document.body.classList.remove("light"); localStorage.setItem('light', 'false') } From df4553eaf230775c55765b82168543959318fede Mon Sep 17 00:00:00 2001 From: xylo Date: Fri, 27 Mar 2026 05:11:40 +0530 Subject: [PATCH 15/33] Bugfix : Better stroke style rendering and smoother shape edges. --- canvas.js | 30 +++++++++++++++++++++++++----- index.html | 1 + 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/canvas.js b/canvas.js index 7711544..6e241d9 100644 --- a/canvas.js +++ b/canvas.js @@ -6,6 +6,8 @@ canvas.width = window.innerWidth; canvas.height = window.innerHeight; canvas2.width = window.innerWidth; canvas2.height = window.innerHeight; +ctx.lineJoin = "round"; +ctx2.lineJoin = "round"; let is_drawing = false; let startX = 0; @@ -29,10 +31,16 @@ function canvas2draw(object) { ctx2.globalAlpha = object.opacity; ctx2.lineDashOffset = 0; ctx2.lineWidth = object.stroke_width; - if(object.stroke_style === "dotted-line"){ - ctx2.setLineDash([2, 5]); + if(object.stroke_style === "dashed-line"){ + ctx2.lineCap = "butt"; + ctx2.setLineDash([2*object.stroke_width, 2*object.stroke_width]); + } + else if(object.stroke_style === "dotted-line"){ + ctx2.lineCap = "round"; + ctx2.setLineDash([0,3*object.stroke_width]); } else{ + ctx2.lineCap = "round"; ctx2.setLineDash([]); } if(object.type === "line"){ @@ -149,11 +157,22 @@ function change_cursor(){ //function to change the stroke style function change_style(){ - if(stroke_style === "dotted-line"){ - ctx.setLineDash([2, 5]); - ctx2.setLineDash([2, 5]); + const stroke_width = JSON.parse(localStorage.getItem("stroke_width")); + if(stroke_style === "dashed-line"){ + ctx.lineCap = "butt"; + ctx2.lineCap = "butt"; + ctx.setLineDash([2*stroke_width, 2*stroke_width]); + ctx2.setLineDash([2*stroke_width, 2*stroke_width]); + } + else if(stroke_style === "dotted-line"){ + ctx.lineCap = "round"; + ctx2.lineCap = "round"; + ctx.setLineDash([0,3*stroke_width]); + ctx2.setLineDash([0,3*stroke_width]); } else{ + ctx.lineCap = "round"; + ctx2.lineCap = "round"; ctx.setLineDash([]); ctx2.setLineDash([]); } @@ -288,6 +307,7 @@ document.getElementById("stroke_width").addEventListener("change", (event) => { localStorage.setItem("stroke_width", JSON.stringify(event.target.value)); ctx.lineWidth = event.target.value; ctx2.lineWidth = event.target.value; + change_style(); }); document.getElementById("font_color").addEventListener("change", (e) => { diff --git a/index.html b/index.html index 55cb411..51f490b 100644 --- a/index.html +++ b/index.html @@ -48,6 +48,7 @@
+
Stroke width:
From 43440de81a744bb3a3aae2f5eaab83a3078485e9 Mon Sep 17 00:00:00 2001 From: xylo Date: Fri, 27 Mar 2026 12:09:48 +0530 Subject: [PATCH 16/33] Feat: Users can add text to the canvas. --- canvas.js | 41 +++++++++++++++++++++++++++++++++-------- index.html | 2 +- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/canvas.js b/canvas.js index 6e241d9..24875e8 100644 --- a/canvas.js +++ b/canvas.js @@ -79,6 +79,12 @@ function canvas2draw(object) { ctx2.stroke(); } } + else if(object.type === "text"){ + ctx2.textBaseline = "top"; + ctx2.font = `${object.font_size} ${object.font_family}`; + ctx2.fillStyle = object.font_color; + ctx2.fillText(`${object.text}`,object.left,object.top); + } } function rerender(){ const undo_stack = JSON.parse(localStorage.getItem("undo_stack")); @@ -112,6 +118,17 @@ function createObject(e){ object.type = "polygon"; object.pencil_array = pencil_array; } + else if(curr_tool === "text"){ + object.type = "text"; + object.font_family = document.getElementById("font_family").value; + object.font_size = `${document.getElementById("font_size").value}px`; + object.font_color = document.getElementById("font_color").value; + const tb = document.getElementById("textbox"); + object.text = tb.value; + object.top = parseFloat(tb.style.top); + object.left = parseFloat(tb.style.left); + document.body.removeChild(tb); + } object.stroke_color = document.getElementById("stroke_color").value; object.opacity = document.getElementById("opacity").value/100; object.stroke_width = document.getElementById("stroke_width").value; @@ -119,7 +136,7 @@ function createObject(e){ let undo_stack = JSON.parse(localStorage.getItem("undo_stack")); undo_stack.push(object); localStorage.setItem("undo_stack", JSON.stringify(undo_stack)); - canvas2draw(object); + canvas2draw(object); } function Shape_obj(endX,endY){ this.startX = startX; @@ -186,18 +203,20 @@ function add_element(s,startX,startY,endX,endY){ input.style.position = "absolute"; input.id = "textbox"; input.style.resize = "none"; + input.wrap = "off"; input.style.top = `${Math.min(startY,endY)}px`; input.style.left = `${Math.min(startX,endX)}px`; input.style.color = `${localStorage.getItem("font_color")}`; input.style.fontFamily = `${localStorage.getItem("font_family")}`; + input.style.fontSize = `${document.getElementById("font_size").value}px` input.style.backgroundColor = "transparent"; - input.style.overflow = "none"; + input.style.overflow = "hidden"; input.style.height = `${Math.abs(startY-endY)}px`; input.style.width = `${Math.abs(startX-endX)}px`; document.body.appendChild(input); input.addEventListener("input",(e)=>{ - input.style.height = `${Math.abs(startY-endY)}px`; - input.style.height = `${input.scrollHeight}px`; + input.style.width = `${Math.abs(startX-endX)}px`; + input.style.width = `${input.scrollWidth}px`; }) input.focus(); input.addEventListener("mousedown",(e)=>{e.stopPropagation()},true); @@ -210,7 +229,7 @@ if(!localStorage.getItem("font_color")){ localStorage.setItem("font_color","#226e08"); } if(!localStorage.getItem("font_size")){ - localStorage.setItem("font_size","1rem"); + localStorage.setItem("font_size",'16'); } if(!localStorage.getItem("font_family")){ localStorage.setItem("font_family","Arial"); @@ -250,6 +269,7 @@ if (!stroke_style) { stroke_style = "straight-line"; } +document.getElementById("font_size").value = localStorage.getItem("font_size"); document.getElementById("font_color").value = localStorage.getItem("font_color"); document.getElementById("font_family").value = localStorage.getItem("font_family"); document.getElementById("stroke_color").value = localStorage.getItem("stroke_color"); @@ -316,6 +336,9 @@ document.getElementById("font_color").addEventListener("change", (e) => { document.getElementById("font_family").addEventListener("change", (e) => { localStorage.setItem("font_family", e.currentTarget.value); }); +document.getElementById("font_size").addEventListener("change", (e) => { + localStorage.setItem("font_size", e.currentTarget.value); +}); //---------------------------------------------------------------------------------------- //live drawing functions : function draw_line(endX,endY) { @@ -370,7 +393,9 @@ canvas.addEventListener("mousedown", (event) => { }) document.body.addEventListener("mousedown" , (e) => { if(curr_tool === "text" && text_box === 1){ - //create ctx2 text + if(document.getElementById("textbox").value !== null){createObject(e);} + else{document.body.removeChild(document.getElementById("textbox")); + } } },false) canvas.addEventListener("click", (event) => { @@ -515,9 +540,9 @@ document.addEventListener('keydown', (event) => { //----------------------------------------------------------------------------------------- //Interactive sliders: -document.getElementById("font_size_value").textContent = `${parseFloat(getComputedStyle(document.documentElement).fontSize)*document.getElementById("font_size").value}px`; +document.getElementById("font_size_value").textContent = `${document.getElementById("font_size").value}px`; document.getElementById("font_size").addEventListener("input", (event) => { -document.getElementById("font_size_value").textContent = `${parseFloat(getComputedStyle(document.documentElement).fontSize)*event.currentTarget.value}px`; +document.getElementById("font_size_value").textContent = `${event.currentTarget.value}px`; }); document.getElementById("opacity_value").textContent = `${document.getElementById("opacity").value/100}`; diff --git a/index.html b/index.html index 51f490b..4ac3843 100644 --- a/index.html +++ b/index.html @@ -69,7 +69,7 @@
Font size:
-
+
From 51081cd1835c108d89e9d71f9588df5003f38a39 Mon Sep 17 00:00:00 2001 From: xylo Date: Fri, 27 Mar 2026 15:00:05 +0530 Subject: [PATCH 17/33] Feat: Users can erase objects on the canvas. --- canvas.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/canvas.js b/canvas.js index 24875e8..4951db9 100644 --- a/canvas.js +++ b/canvas.js @@ -20,10 +20,35 @@ let stroke_style = localStorage.getItem("stroke_style"); let text_box = 0; let mouse_downed_text = 0; console.log(localStorage.getItem("undo_stack")); +let state_array = []; //----------------------------------------------------------------------------------------- //main funcs - +//convert to path2d: +function convert_to_path2d(object){ + let path = new Path2D(); + if(object.type === "line"){ + path.moveTo(object.startX,object.startY); + path.lineTo(object.endX,object.endY); + } + else if(object.type === "rectangle" || object.type === "text"){ + path.rect(object.startX,object.startY,object.endX-object.startX,object.endY-object.startY); + } + else if(object.type === "circle"){ + path.ellipse((object.startX+object.endX)/2, (object.endY+object.startY)/2,Math.abs((object.endX-object.startX)/2),Math.abs((object.endY-object.startY)/2),0,0,2*Math.PI) + } + else if(object.type === "pencil" || object.type === "polygon") + { + for(let i = 1; i < object.pencil_array.length; i++){ + path.moveTo(object.pencil_array[i-1].x, object.pencil_array[i-1].y); + path.lineTo(object.pencil_array[i].x,object.pencil_array[i].y); + } + if(object.type === "polygon"){ + path.closePath(); + } + } + return path; +} //canvas2 drawing function function canvas2draw(object) { ctx.clearRect(0,0,canvas.width,canvas.height); @@ -83,7 +108,7 @@ function canvas2draw(object) { ctx2.textBaseline = "top"; ctx2.font = `${object.font_size} ${object.font_family}`; ctx2.fillStyle = object.font_color; - ctx2.fillText(`${object.text}`,object.left,object.top); + ctx2.fillText(`${object.text}`,object.startX,object.startY); } } function rerender(){ @@ -94,6 +119,9 @@ function rerender(){ //Object constructors function createObject(e){ is_drawing = false; + if(curr_tool === "eraser"){ + return; + } let object = {}; if(curr_tool === "line"){ let obj = new Shape_obj(e.clientX,e.clientY); @@ -125,8 +153,10 @@ function createObject(e){ object.font_color = document.getElementById("font_color").value; const tb = document.getElementById("textbox"); object.text = tb.value; - object.top = parseFloat(tb.style.top); - object.left = parseFloat(tb.style.left); + object.startY = parseFloat(tb.style.top); + object.startX = parseFloat(tb.style.left); + object.endY = parseFloat(tb.scrollHeight) + object.startY; + object.endX = parseFloat(tb.scrollWidth) + object.startX; document.body.removeChild(tb); } object.stroke_color = document.getElementById("stroke_color").value; @@ -136,6 +166,7 @@ function createObject(e){ let undo_stack = JSON.parse(localStorage.getItem("undo_stack")); undo_stack.push(object); localStorage.setItem("undo_stack", JSON.stringify(undo_stack)); + state_array.push(convert_to_path2d(object)); canvas2draw(object); } function Shape_obj(endX,endY){ @@ -222,7 +253,18 @@ function add_element(s,startX,startY,endX,endY){ input.addEventListener("mousedown",(e)=>{e.stopPropagation()},true); } } - +function erase(x,y){ + for(let i = state_array.length - 1 ; i > -1;i--){ + if(ctx2.isPointInStroke(state_array[i],x,y) || ctx2.isPointInPath(state_array[i],x,y)){ + state_array.splice(i,1); + let arr = JSON.parse(localStorage.getItem("undo_stack")); + arr.splice(i,1); + localStorage.setItem("undo_stack",JSON.stringify(arr)); + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + rerender(); + } + } +} //------------------------------Initializers----------------------------------------------- if(!localStorage.getItem("font_color")){ @@ -281,6 +323,10 @@ document.getElementById(stroke_style).classList.add("selected"); change_toolbar(localStorage.getItem("toolbar")) change_cursor(); change_style(); +{const undo_stack = JSON.parse(localStorage.getItem("undo_stack")); + for(let item of undo_stack){ + state_array.push(convert_to_path2d(item)); +}} ctx.lineWidth = JSON.parse(localStorage.getItem("stroke_width")); ctx.globalAlpha = JSON.parse(localStorage.getItem("opacity")); ctx.strokeStyle = localStorage.getItem("stroke_color"); @@ -297,7 +343,7 @@ for (const tool of tools) { change_toolbar("font"); localStorage.setItem("toolbar", "font") } - else if(tool.id === "selection"){ + else if(tool.id === "selection" || tool.id === "eraser"){ change_toolbar("selection"); localStorage.setItem("toolbar","selection"); } @@ -455,6 +501,9 @@ canvas.addEventListener("mousemove", (event) => { draw_rectangle(event.clientX, event.clientY); } } + else if(curr_tool === "eraser"){ + erase(event.clientX,event.clientY); + } } }) @@ -507,6 +556,7 @@ function undo(e){ if(arr.length !== 0) { const obj = arr.pop(); + state_array.pop(); arr2.push(obj); localStorage.setItem("undo_stack", JSON.stringify(arr)); localStorage.setItem("redo_stack", JSON.stringify(arr2)); @@ -521,6 +571,7 @@ function redo(e){ if(arr2.length !== 0){ const obj = arr2.pop(); arr.push(obj); + state_array.push(convert_to_path2d(obj)); localStorage.setItem("undo_stack", JSON.stringify(arr)); localStorage.setItem("redo_stack", JSON.stringify(arr2)); canvas2draw(obj); From 862b9128ef26ce2af19fbaf0b5aab3bbb53d1342 Mon Sep 17 00:00:00 2001 From: xylo Date: Fri, 27 Mar 2026 20:10:56 +0530 Subject: [PATCH 18/33] Feat: Users can select and move objects on the canvas. --- apollo.css | 3 ++ canvas.js | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 142 insertions(+), 8 deletions(-) diff --git a/apollo.css b/apollo.css index eb29e13..4669176 100644 --- a/apollo.css +++ b/apollo.css @@ -40,6 +40,9 @@ button { .textcursor { cursor : text; } +.grabbing { + cursor : grabbing; +} #top_buttons { background-color: var(--button-background-color); position: fixed; diff --git a/canvas.js b/canvas.js index 4951db9..dc52fd7 100644 --- a/canvas.js +++ b/canvas.js @@ -21,9 +21,25 @@ let text_box = 0; let mouse_downed_text = 0; console.log(localStorage.getItem("undo_stack")); let state_array = []; - +let move_mode = 0; +let selected_index = -1; +//mousedown -> draw dotted lines //----------------------------------------------------------------------------------------- //main funcs +//select func: +function select(event){ + const x = event.clientX; + const y = event.clientY; + for(let i = state_array.length - 1 ; i >= 0 ; i--){ + if(ctx2.isPointInPath(state_array[i],x,y) || ctx2.isPointInStroke(state_array[i],x,y)){ + selected_index = i; + canvas.classList.add("grabbing"); + return; + } + } + selected_index = -1; + canvas.classList.remove("grabbing"); +} //convert to path2d: function convert_to_path2d(object){ let path = new Path2D(); @@ -194,11 +210,8 @@ function change_toolbar(s){ //function to change the cursor function change_cursor(){ - canvas.classList.remove("crosshair", "textcursor"); - if (curr_tool === "text") { - canvas.classList.add("textcursor"); - } - else if (curr_tool !== "selection") { + canvas.classList.remove("crosshair", "textcursor", "grabbing"); + if (curr_tool !== "selection") { canvas.classList.add("crosshair"); } } @@ -414,7 +427,98 @@ function draw_free(endX, endY) { startX = endX; startY = endY; } +function draw_box_outline(object){ + const dir_x = (object.startX - object.endX)/Math.abs(object.startX - object.endX); + const dir_y = (object.startY - object.endY)/Math.abs(object.startY - object.endY); + object.startX = object.startX + 15*dir_x; + object.startY = object.startY + 15*dir_y; + object.endX = object.endX - 15*dir_x; + object.endY = object.endY - 15*dir_y; + ctx.beginPath(); + ctx.moveTo(object.startX,object.startY); + ctx.arc(object.startX,object.startY,5,0,2*Math.PI); + ctx.moveTo(object.endX,object.startY); + ctx.arc(object.endX,object.startY,5,0,2*Math.PI); + ctx.moveTo(object.endX,object.endY); + ctx.arc(object.endX,object.endY,5,0,2*Math.PI); + ctx.moveTo(object.startX,object.endY); + ctx.arc(object.startX,object.endY,5,0,2*Math.PI); + ctx.fill(); + ctx.moveTo(object.startX,object.startY); + ctx.lineTo(object.endX,object.startY); + ctx.lineTo(object.endX,object.endY); + ctx.lineTo(object.startX,object.endY); + ctx.lineTo(object.startX,object.startY); + ctx.stroke() +} +function draw_outline(object){ + ctx.lineWidth = "2"; + ctx.lineJoin = "round"; + ctx.lineCap = "round"; + ctx.strokeStyle = "#16a7f5" + ctx.fillStyle = "#16a7f5" + ctx.globalAlpha = "1"; + ctx.setLineDash([]); + ctx.clearRect(0,0,canvas.width,canvas.height); + if(object.type === "line"){ + ctx.beginPath(); + ctx.moveTo(object.startX,object.startY); + ctx.arc(object.startX,object.startY,5,0,2*Math.PI); + ctx.moveTo((object.startX + object.endX)/2,(object.startY + object.endY)/2); + ctx.arc((object.endX + object.startX)/2,(object.endY + object.startY)/2,5,0,2*Math.PI); + ctx.moveTo(object.endX,object.endY); + ctx.arc(object.endX,object.endY,5,0,2*Math.PI); + ctx.fill(); + } + else if(object.type === "rectangle" || object.type === "circle" || object.type === "text"){ + draw_box_outline(object); + } + else if(object.type === "pencil" || object.type === "polygon"){ + let max_x = object.pencil_array[0].x; + let min_x = object.pencil_array[0].x; + let max_y = object.pencil_array[0].y; + let min_y = object.pencil_array[0].y; + for(let i = 1 ; i < object.pencil_array.length; i++){ + max_x = Math.max(object.pencil_array[i].x,max_x); + max_y = Math.max(object.pencil_array[i].y,max_y); + min_x = Math.min(object.pencil_array[i].x,min_x); + min_y = Math.min(object.pencil_array[i].y,min_y); + } + object.startX = min_x; + object.endX = max_x; + object.startY = min_y; + object.endY = max_y; + draw_box_outline(object); + } + change_style(); + ctx.lineWidth = document.getElementById("stroke_width").value; + ctx.globalAlpha = document.getElementById("opacity").value/100; + ctx.strokeStyle = document.getElementById("stroke_color").value; +} + +function move(disp_x,disp_y){ + let arr = JSON.parse(localStorage.getItem("undo_stack")); + let object = arr[selected_index]; + if(object.type === "pencil" || object.type === "polygon"){ + for(let i = 0 ; i < object.pencil_array.length ; i++){ + object.pencil_array[i].x += disp_x; + object.pencil_array[i].y += disp_y; + } + } + else{ + object.startX += disp_x; + object.endX += disp_x; + object.endY += disp_y; + object.startY += disp_y; + } + arr[selected_index] = object; + state_array[selected_index] = convert_to_path2d(object); + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + localStorage.setItem("undo_stack",JSON.stringify(arr)); + rerender(); + draw_outline(object); +} //----------------------------------------------------------------------------------------- //event listeners canvas.addEventListener("mousedown", (event) => { @@ -424,6 +528,17 @@ canvas.addEventListener("mousedown", (event) => { if(curr_tool === "pencil"){ pencil_array = [{x: startX, y: startY}]; } + else if(curr_tool === "selection"){ + if(selected_index !== -1){ + move_mode = 1; + startX = event.clientX; + startY = event.clientY; + draw_outline(JSON.parse(localStorage.getItem("undo_stack"))[selected_index]); + } + else{ + ctx.clearRect(0,0,canvas.width,canvas.height); + } + } else if(curr_tool === "text"){ if(text_box === 0){ mouse_downed_text = 1; @@ -504,6 +619,17 @@ canvas.addEventListener("mousemove", (event) => { else if(curr_tool === "eraser"){ erase(event.clientX,event.clientY); } + + } + if(curr_tool === "selection"){ + if(move_mode === 0){ + select(event); + } + else{ + move(event.clientX - startX,event.clientY - startY); + startX = event.clientX; + startY = event.clientY; + } } }) @@ -519,15 +645,20 @@ canvas.addEventListener("mouseup", (e) => { else if(curr_tool === "text" && text_box === 1){ text_box = 0; } + else if(curr_tool === "selection"){ + move_mode = 0; + } else if(curr_tool !== "polygon"){ createObject(e); - }} + } + -); +}); //to prevent glitches when mouse leaves canvas canvas.addEventListener("mouseleave", (e) => { in_poly_mode = false; + move_mode = 0; if(is_drawing === true){ createObject(e); } From 6e459dcbf8976940a77f35483aeab48a2133a1b7 Mon Sep 17 00:00:00 2001 From: xylo Date: Fri, 27 Mar 2026 20:14:21 +0530 Subject: [PATCH 19/33] bugfix : Faulty image icon --- index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/index.html b/index.html index 4ac3843..697f2aa 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,6 @@ - From f070a929596dac29c89fd3ee57ee0b4d847e9b44 Mon Sep 17 00:00:00 2001 From: xylo Date: Sat, 28 Mar 2026 00:28:17 +0530 Subject: [PATCH 20/33] feat: User controls the color of the canvas and it is theme independent. --- apollo.css | 49 ++++++++++++++++++++++++++++++++++++++----------- index.html | 7 ++++--- toggle_theme.js | 13 +++++++++---- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/apollo.css b/apollo.css index 4669176..181eb21 100644 --- a/apollo.css +++ b/apollo.css @@ -7,8 +7,7 @@ } .light { - --bg-color: #cbcbcb ; - --button-background-color:#ffffe3 ; + --button-background-color:#DFD0B8; --button-highlight-color: black; --button-fill-color: #4a4a4a; --button-active-color:#6d8196 ; @@ -49,11 +48,12 @@ button { display: flex; align-items: stretch; gap: 1%; + margin: 0 0 0 0; border:0; padding:0; width: clamp(12rem,50%,50rem); left: 25%; - top: 0rem; + top: 2vh; right : 25%; height: 5%; border-radius: 8rem; @@ -85,8 +85,6 @@ button { } .extra_functionality { - position: fixed; - top:0.6875rem; height: 6vh; aspect-ratio: 1/1; display: flex; @@ -106,8 +104,8 @@ button { } .history { position: fixed; - top:0.6875rem; - left:1.25rem; + top:2vh; + left:2vw; height: 5vh; aspect-ratio: 2.5/1; display: flex; @@ -133,10 +131,41 @@ button { stroke: var(--button-highlight-color); stroke-width: 1.5; } +#theme { + position: fixed; + right:1vw; + top:1vw; + height: 5vh; + display:flex; + gap:1vw; + background-color: var(--button-background-color); + flex-direction: row; + justify-content: center; + align-items: center; + padding:0 1vw; + margin:0 0; + border-radius: 4rem; +} #toggle_theme { - right:20px; + height: 100%; + aspect-ratio: 1/1; + display: flex; + border-radius: 50%; + padding:0; + margin:0; + background-color: transparent; + z-index:1; +} +#canvas_color{ + height:3.5vh; + border:none; + cursor:pointer; + background-color: transparent; } +#toggle_theme svg:hover{ + stroke: var(--button-highlight-color) +} #toggle_theme svg:last-child { display: none; @@ -148,8 +177,6 @@ button { display: none; } - - #test { position: fixed; background-color: transparent; @@ -174,7 +201,7 @@ button { background-color: var(--button-background-color); border-radius: 1rem; position: fixed; - top: 20%; + top: 20vh; height:auto; max-height: 70vh; gap: 0.7rem; diff --git a/index.html b/index.html index 697f2aa..5a0a2b3 100644 --- a/index.html +++ b/index.html @@ -23,13 +23,14 @@ -
- - +
+
From 6f0af9e1b1bf113989c340ea962f12749f60e5d2 Mon Sep 17 00:00:00 2001 From: xylo Date: Sat, 28 Mar 2026 12:51:39 +0530 Subject: [PATCH 22/33] bugfix: Select tool works better with polygons and faster render times for images --- canvas.js | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/canvas.js b/canvas.js index cb54364..14d4abf 100644 --- a/canvas.js +++ b/canvas.js @@ -22,6 +22,7 @@ let mouse_downed_text = 0; console.log(localStorage.getItem("undo_stack")); let state_array = []; let move_mode = 0; +let image_cache = {}; let selected_index = -1; //mousedown -> draw dotted lines //----------------------------------------------------------------------------------------- @@ -55,8 +56,8 @@ function convert_to_path2d(object){ } else if(object.type === "pencil" || object.type === "polygon") { + path.moveTo(object.pencil_array[0].x, object.pencil_array[0].y); for(let i = 1; i < object.pencil_array.length; i++){ - path.moveTo(object.pencil_array[i-1].x, object.pencil_array[i-1].y); path.lineTo(object.pencil_array[i].x,object.pencil_array[i].y); } if(object.type === "polygon"){ @@ -126,15 +127,27 @@ function canvas2draw(object) { ctx2.fillText(`${object.text}`,object.startX,object.startY); } else if(object.type === "image"){ - const img = new Image(); - img.src = object.imgdata; - img.onload = () =>{ + const x = Math.min(object.startX, object.endX); + const y = Math.min(object.startY, object.endY); + const w = Math.abs(object.endX - object.startX); + const h = Math.abs(object.endY - object.startY); + if (image_cache[object.imgdata]) { ctx2.save(); - ctx2.drawImage(img, Math.min(object.startX,object.endX), Math.min(object.startY,object.endY)); + ctx2.drawImage(image_cache[object.imgdata], x, y, w, h); ctx2.restore(); - } + } else { + const img = new Image(); + img.src = object.imgdata; + img.onload = () => { + image_cache[object.imgdata] = img; + ctx2.save(); + ctx2.drawImage(img, x, y, w, h); + ctx2.restore(); + } + } } + } function rerender(){ const undo_stack = JSON.parse(localStorage.getItem("undo_stack")); @@ -151,8 +164,10 @@ function createObject(e){ let object = {}; if (curr_tool === "image"){ let obj = new Shape_obj(e.clientX, e.clientY); - const width = Math.abs(obj.endX - obj.startX); - const height = Math.abs(obj.endY - obj.startY); + if (Math.abs(obj.endX - obj.startX) < 30) obj.endX = obj.startX + 30; + if (Math.abs(obj.endY - obj.startY) < 30) obj.endY = obj.startY + 30; + const width = Math.max(30,Math.abs(obj.endX - obj.startX)); + const height = Math.max(30,Math.abs(obj.endY - obj.startY)); const img = new Image(); img.crossOrigin = "anonymous"; img.src = `https://picsum.photos/${width}/${height}`; From 45210faffe4c4f392bd9c9f7c7ea4d0a7e6cf518 Mon Sep 17 00:00:00 2001 From: xylo Date: Sat, 28 Mar 2026 16:44:24 +0530 Subject: [PATCH 23/33] Functionality : Improved UI and responsiveness. --- apollo.css | 69 +++++++++++++++++++++++++++++++++++++++++++++++------- canvas.js | 62 +++++++++++++++++++++++++++++++++++++----------- index.html | 2 ++ 3 files changed, 111 insertions(+), 22 deletions(-) diff --git a/apollo.css b/apollo.css index 181eb21..58a40ea 100644 --- a/apollo.css +++ b/apollo.css @@ -42,6 +42,9 @@ button { .grabbing { cursor : grabbing; } +.erasing { + cursor: url("data:image/svg+xml;utf8,") 12 12, pointer; +} #top_buttons { background-color: var(--button-background-color); position: fixed; @@ -51,7 +54,7 @@ button { margin: 0 0 0 0; border:0; padding:0; - width: clamp(12rem,50%,50rem); + width: 50%; left: 25%; top: 2vh; right : 25%; @@ -108,18 +111,21 @@ button { left:2vw; height: 5vh; aspect-ratio: 2.5/1; + width: auto; display: flex; justify-content: center; align-items: center; border-radius: 1rem; - padding:0; - margin:0; + margin: 0; + gap:1vw; background-color: var(--button-background-color); z-index:1; } .history button svg{ - height:60%; - width:100%; + height:2vw; + width:2vw; + min-height: 30px; + min-width: 30px; fill:none; stroke: var(--button-fill-color); } @@ -134,7 +140,7 @@ button { #theme { position: fixed; right:1vw; - top:1vw; + top:2vh; height: 5vh; display:flex; gap:1vw; @@ -176,7 +182,9 @@ button { .light #toggle_theme svg:first-child { display: none; } - +#hint{ + display: none; +} #test { position: fixed; background-color: transparent; @@ -202,10 +210,10 @@ button { border-radius: 1rem; position: fixed; top: 20vh; + left:1vw; height:auto; max-height: 70vh; gap: 0.7rem; - left:0rem; } .beautifiers pre{ margin:0; @@ -242,4 +250,49 @@ button { output{ color: var(--button-fill-color); } +#options{ + display : none; +} + +@media (max-width: 620px) { + #options{ + display: flex; + position: fixed; + top:auto; + background-color: var(--button-background-color); + font-size: 1rem; + color: var(--button-fill-color); + bottom: 2vh; + height:6vh; + left:40vw; + right:40vw; + width:20vw; + } + #options :hover{ + color: var(--button-highlight-color); + } + #top_buttons{ + width:90vw; + left:5vw; + height:6vh; + right:5vw + } + #theme{ + top:auto; + height:6vh; + bottom:2vh; + } + .history{ + top:auto; + height:6vh; + bottom:2vh; + } + .beautifiers{ + left:15vw; + right: 15vw; + height:50vh; + width:70vw; + } + +} diff --git a/canvas.js b/canvas.js index 14d4abf..628cbfa 100644 --- a/canvas.js +++ b/canvas.js @@ -24,6 +24,7 @@ let state_array = []; let move_mode = 0; let image_cache = {}; let selected_index = -1; +let toolbar_state = 1; //mousedown -> draw dotted lines //----------------------------------------------------------------------------------------- //main funcs @@ -247,24 +248,32 @@ function Shape_obj(endX,endY){ //function to switch between font toolbar and shapes toolbar function change_toolbar(s){ - if(s === "font"){ - document.getElementById("font_manipulation").style.display = "flex"; - document.getElementById("stroke_manipulation").style.display = "none"; - } - else if(s === "stroke"){ - document.getElementById("font_manipulation").style.display = "none"; - document.getElementById("stroke_manipulation").style.display = "flex"; - } - else if(s === "selection"){ - document.getElementById("font_manipulation").style.display = "none"; - document.getElementById("stroke_manipulation").style.display = "none"; + if(toolbar_state === 1){ + if(s === "font"){ + document.getElementById("font_manipulation").style.display = "flex"; + document.getElementById("stroke_manipulation").style.display = "none"; + } + else if(s === "stroke"){ + document.getElementById("font_manipulation").style.display = "none"; + document.getElementById("stroke_manipulation").style.display = "flex"; + } + else if(s === "selection"){ + document.getElementById("font_manipulation").style.display = "none"; + document.getElementById("stroke_manipulation").style.display = "none"; + } } } //function to change the cursor function change_cursor(){ - canvas.classList.remove("crosshair", "textcursor", "grabbing"); - if (curr_tool !== "selection") { + canvas.classList.remove("crosshair", "textcursor", "erasing", "grabbing"); + if(curr_tool === "text"){ + canvas.classList.add("textcursor"); + } + else if(curr_tool === "eraser"){ + canvas.classList.add("erasing"); + } + else if (curr_tool !== "selection") { canvas.classList.add("crosshair"); } } @@ -409,7 +418,7 @@ for (const tool of tools) { change_toolbar("font"); localStorage.setItem("toolbar", "font") } - else if(tool.id === "selection" || tool.id === "eraser"){ + else if(tool.id === "selection" || tool.id === "eraser" || tool.id === "image"){ change_toolbar("selection"); localStorage.setItem("toolbar","selection"); } @@ -418,6 +427,12 @@ for (const tool of tools) { change_toolbar("stroke"); localStorage.setItem("toolbar","stroke"); } + if(tool.id === "polygon"){ + document.getElementById("hint").style.display = "flex"; + } + else{ + document.getElementById("hint").style.display = "none"; + } document.getElementById(curr_tool).classList.remove("selected"); localStorage.setItem("curr_tool", event.currentTarget.id); curr_tool = localStorage.getItem("curr_tool"); @@ -451,6 +466,25 @@ document.getElementById("font_family").addEventListener("change", (e) => { document.getElementById("font_size").addEventListener("change", (e) => { localStorage.setItem("font_size", e.currentTarget.value); }); +document.getElementById("options").addEventListener("click", (e)=>{ + if(toolbar_state === 0){ + toolbar_state = 1; + if(curr_tool === "eraser" || curr_tool === "selection" || curr_tool === "image"){ + change_toolbar("selection"); + } + else if(curr_tool === "text"){ + change_toolbar("font"); + } + else{ + change_toolbar("stroke"); + } + + } + else{ + change_toolbar("selection"); + toolbar_state = 0; + } +}) //---------------------------------------------------------------------------------------- //live drawing functions : function draw_line(endX,endY) { diff --git a/index.html b/index.html index eef38e5..593045b 100644 --- a/index.html +++ b/index.html @@ -55,6 +55,7 @@
Opacity:
+
Double-click to stop!
Text color:
@@ -72,6 +73,7 @@
Font size:
+ From bf5e71f132e08d8f283f8ca6e9e84a1ddf495e25 Mon Sep 17 00:00:00 2001 From: xylo Date: Sat, 28 Mar 2026 18:50:11 +0530 Subject: [PATCH 24/33] Bugfix: Opacity of images is independent of opacity of other objects. Line selection box enhanced. --- canvas.js | 55 ++++++++++++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/canvas.js b/canvas.js index 628cbfa..22b5c4d 100644 --- a/canvas.js +++ b/canvas.js @@ -28,20 +28,7 @@ let toolbar_state = 1; //mousedown -> draw dotted lines //----------------------------------------------------------------------------------------- //main funcs -//select func: -function select(event){ - const x = event.clientX; - const y = event.clientY; - for(let i = state_array.length - 1 ; i >= 0 ; i--){ - if(ctx2.isPointInPath(state_array[i],x,y) || ctx2.isPointInStroke(state_array[i],x,y)){ - selected_index = i; - canvas.classList.add("grabbing"); - return; - } - } - selected_index = -1; - canvas.classList.remove("grabbing"); -} + //convert to path2d: function convert_to_path2d(object){ let path = new Path2D(); @@ -82,7 +69,7 @@ function canvas2draw(object) { ctx2.setLineDash([0,3*object.stroke_width]); } else{ - ctx2.lineCap = "round"; + ctx2.lineCap = "butt"; ctx2.setLineDash([]); } if(object.type === "line"){ @@ -142,6 +129,7 @@ function canvas2draw(object) { img.onload = () => { image_cache[object.imgdata] = img; ctx2.save(); + ctx2.globalAlpha = 1; ctx2.drawImage(img, x, y, w, h); ctx2.restore(); @@ -294,8 +282,8 @@ function change_style(){ ctx2.setLineDash([0,3*stroke_width]); } else{ - ctx.lineCap = "round"; - ctx2.lineCap = "round"; + ctx.lineCap = "butt"; + ctx2.lineCap = "butt"; ctx.setLineDash([]); ctx2.setLineDash([]); } @@ -548,20 +536,7 @@ function draw_outline(object){ ctx.globalAlpha = "1"; ctx.setLineDash([]); ctx.clearRect(0,0,canvas.width,canvas.height); - if(object.type === "line"){ - ctx.beginPath(); - ctx.moveTo(object.startX,object.startY); - ctx.arc(object.startX,object.startY,5,0,2*Math.PI); - ctx.moveTo((object.startX + object.endX)/2,(object.startY + object.endY)/2); - ctx.arc((object.endX + object.startX)/2,(object.endY + object.startY)/2,5,0,2*Math.PI); - ctx.moveTo(object.endX,object.endY); - ctx.arc(object.endX,object.endY,5,0,2*Math.PI); - ctx.fill(); - } - else if(object.type === "rectangle" || object.type === "circle" || object.type === "text" || object.type === "image"){ - draw_box_outline(object); - } - else if(object.type === "pencil" || object.type === "polygon"){ + if(object.type === "pencil" || object.type === "polygon"){ let max_x = object.pencil_array[0].x; let min_x = object.pencil_array[0].x; let max_y = object.pencil_array[0].y; @@ -578,6 +553,9 @@ function draw_outline(object){ object.endY = max_y; draw_box_outline(object); } + else{ + draw_box_outline(object); + } ctx.restore(); } @@ -604,6 +582,20 @@ function move(disp_x,disp_y){ rerender(); draw_outline(object); } +//select func: +function select(event){ + const x = event.clientX; + const y = event.clientY; + for(let i = state_array.length - 1 ; i >= 0 ; i--){ + if(ctx2.isPointInPath(state_array[i],x,y) || ctx2.isPointInStroke(state_array[i],x,y)){ + selected_index = i; + canvas.classList.add("grabbing"); + return; + } + } + selected_index = -1; + canvas.classList.remove("grabbing"); +} //----------------------------------------------------------------------------------------- //event listeners canvas.addEventListener("mousedown", (event) => { @@ -644,6 +636,7 @@ document.body.addEventListener("mousedown" , (e) => { } } },false) + canvas.addEventListener("click", (event) => { if(curr_tool === "polygon"){ is_drawing = true; From f1cb1e1ea5a50ee9c16909eeb3276319bb2ee301 Mon Sep 17 00:00:00 2001 From: xylo Date: Sun, 29 Mar 2026 10:41:16 +0530 Subject: [PATCH 25/33] bugfix: slider for stroke width updates properly on refresh --- canvas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvas.js b/canvas.js index 22b5c4d..7ab173e 100644 --- a/canvas.js +++ b/canvas.js @@ -439,7 +439,7 @@ for (const butt of stroke_buttons){ } )}; document.getElementById("stroke_width").addEventListener("change", (event) => { - localStorage.setItem("stroke_width", JSON.stringify(event.target.value)); + localStorage.setItem("stroke_width", event.target.value); ctx.lineWidth = event.target.value; ctx2.lineWidth = event.target.value; change_style(); From 81435cb94391dd5c2a01e9c03625d7c1c097fde0 Mon Sep 17 00:00:00 2001 From: xylo Date: Sun, 29 Mar 2026 18:29:17 +0530 Subject: [PATCH 26/33] refactor : change object storage techniques --- canvas.js | 175 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 109 insertions(+), 66 deletions(-) diff --git a/canvas.js b/canvas.js index 7ab173e..d7336d6 100644 --- a/canvas.js +++ b/canvas.js @@ -23,13 +23,36 @@ console.log(localStorage.getItem("undo_stack")); let state_array = []; let move_mode = 0; let image_cache = {}; -let selected_index = -1; +let is_selected = 0; let toolbar_state = 1; +let current_handles = {tl:{},tr:{},bl:{},br:{},rot:{}}; //mousedown -> draw dotted lines //----------------------------------------------------------------------------------------- //main funcs //convert to path2d: +function translater(x,y){ + let arr = JSON.parse(localStorage.getItem("undo_stack")); + let object = arr[selected_index]; + if(object.type === "pencil" || object.type === "polygon"){ + for(let i = 0 ; i < object.pencil_array.length ; i++){ + object.pencil_array[i].x += x; + object.pencil_array[i].y += y; + } + } + else{ + object.startX += x; + object.endX += x; + object.endY += y; + object.startY += y; + } + arr[selected_index] = object; + state_array[selected_index] = convert_to_path2d(object); + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + localStorage.setItem("undo_stack",JSON.stringify(arr)); + rerender(); + draw_outline(object); +} function convert_to_path2d(object){ let path = new Path2D(); if(object.type === "line"){ @@ -44,9 +67,9 @@ function convert_to_path2d(object){ } else if(object.type === "pencil" || object.type === "polygon") { - path.moveTo(object.pencil_array[0].x, object.pencil_array[0].y); + path.moveTo(object.centre.x + object.pencil_array[0].x, object.centre.y + object.pencil_array[0].y); for(let i = 1; i < object.pencil_array.length; i++){ - path.lineTo(object.pencil_array[i].x,object.pencil_array[i].y); + path.lineTo(object.centre.x + object.pencil_array[i].x,object.centre.y + object.pencil_array[i].y); } if(object.type === "polygon"){ path.closePath(); @@ -56,6 +79,9 @@ function convert_to_path2d(object){ } //canvas2 drawing function function canvas2draw(object) { + ctx2.save(); + ctx2.setTransform(new DOMMatrix(object.transform)); + ctx2.translate(object.centre.x,object.centre.y); ctx2.strokeStyle = object.stroke_color; ctx2.globalAlpha = object.opacity; ctx2.lineDashOffset = 0; @@ -120,9 +146,7 @@ function canvas2draw(object) { const w = Math.abs(object.endX - object.startX); const h = Math.abs(object.endY - object.startY); if (image_cache[object.imgdata]) { - ctx2.save(); ctx2.drawImage(image_cache[object.imgdata], x, y, w, h); - ctx2.restore(); } else { const img = new Image(); img.src = object.imgdata; @@ -130,12 +154,15 @@ function canvas2draw(object) { image_cache[object.imgdata] = img; ctx2.save(); ctx2.globalAlpha = 1; + ctx2.setTransform(new DOMMatrix(object.transform)); + ctx2.translate(object.centre.x,object.centre.y); ctx2.drawImage(img, x, y, w, h); ctx2.restore(); } } } + ctx2.restore(); } function rerender(){ @@ -145,6 +172,7 @@ function rerender(){ }} //Object constructors function createObject(e){ + const matrix = ctx.getTransform(); ctx.clearRect(0,0,canvas.width,canvas.height); is_drawing = false; if(curr_tool === "eraser"){ @@ -173,6 +201,18 @@ function createObject(e){ let object = obj; object.type = "image"; object.imgdata = reader.result; + object.transform = matrix.toString(); + object.box = {}; + object.centre = {}; + object.box.startX = Math.min(object.startX, object.endX) - 15; + object.box.startY = Math.min(object.startY,object.endY) - 15; + object.box.endX = Math.max(object.endX,object.startX) + 15; + object.box.endY = Math.max(object.endY,object.startY) + 15; + object.centre = {x:(object.box.startX + object.box.endX)/2, y:(object.box.endY + object.box.startY)/2}; + object.startX = object.startX - object.centre.x; + object.startY = object.startY - object.centre.y; + object.endX = object.endX - object.centre.x; + object.endY = object.endY - object.centre.y; let undo_stack = JSON.parse(localStorage.getItem("undo_stack")); undo_stack.push(object); localStorage.setItem("undo_stack", JSON.stringify(undo_stack)); @@ -217,10 +257,46 @@ function createObject(e){ object.endX = parseFloat(tb.scrollWidth) + object.startX; document.body.removeChild(tb); } + if(object.type === "pencil" || object.type === "polygon"){ + let max_x = object.pencil_array[0].x; + let min_x = object.pencil_array[0].x; + let max_y = object.pencil_array[0].y; + let min_y = object.pencil_array[0].y; + for(let i = 1 ; i < object.pencil_array.length; i++){ + max_x = Math.max(object.pencil_array[i].x,max_x); + max_y = Math.max(object.pencil_array[i].y,max_y); + min_x = Math.min(object.pencil_array[i].x,min_x); + min_y = Math.min(object.pencil_array[i].y,min_y); + } + object.startX = min_x; + object.endX = max_x; + object.startY = min_y; + object.endY = max_y; + } + object.transform = matrix.toString(); object.stroke_color = document.getElementById("stroke_color").value; object.opacity = document.getElementById("opacity").value/100; object.stroke_width = document.getElementById("stroke_width").value; object.stroke_style = stroke_style; + object.box = {}; + object.centre = {}; + object.box.startX = Math.min(object.startX, object.endX) - 15; + object.box.startY = Math.min(object.startY,object.endY) - 15; + object.box.endX = Math.max(object.endX,object.startX) + 15; + object.box.endY = Math.max(object.endY,object.startY) + 15; + object.box.rotate_handle_x = (object.box.startX + object.box.endX)/2; + object.box.rotate_handle_y = object.box.startY - 30; + object.centre = {x:(object.box.startX + object.box.endX)/2, y:(object.box.endY + object.box.startY)/2}; + if(object.type === "pencil" || object.type === "polygon"){ + for(let i = 0 ; i < object.pencil_array.length; i++){ + object.pencil_array[i].x -= object.centre.x; + object.pencil_array[i].y -= object.centre.y; + } + } + object.startX = object.startX - object.centre.x; + object.startY = object.startY - object.centre.y; + object.endX = object.endX - object.centre.x; + object.endY = object.endY - object.centre.y; let undo_stack = JSON.parse(localStorage.getItem("undo_stack")); undo_stack.push(object); localStorage.setItem("undo_stack", JSON.stringify(undo_stack)); @@ -503,27 +579,24 @@ function draw_free(endX, endY) { startY = endY; } function draw_box_outline(object){ - const dir_x = (object.startX - object.endX)/Math.abs(object.startX - object.endX); - const dir_y = (object.startY - object.endY)/Math.abs(object.startY - object.endY); - object.startX = object.startX + 15*dir_x; - object.startY = object.startY + 15*dir_y; - object.endX = object.endX - 15*dir_x; - object.endY = object.endY - 15*dir_y; ctx.beginPath(); - ctx.moveTo(object.startX,object.startY); - ctx.arc(object.startX,object.startY,5,0,2*Math.PI); - ctx.moveTo(object.endX,object.startY); - ctx.arc(object.endX,object.startY,5,0,2*Math.PI); - ctx.moveTo(object.endX,object.endY); - ctx.arc(object.endX,object.endY,5,0,2*Math.PI); - ctx.moveTo(object.startX,object.endY); - ctx.arc(object.startX,object.endY,5,0,2*Math.PI); + ctx.moveTo(object.box.rotate_handle_x, object.box.rotate_handle_y); + ctx.arc(object.box.rotate_handle_x,object.box.rotate_handle_y,5,0,2*Math.PI); + ctx.moveTo(object.box.startX,object.box.startY); + ctx.arc(object.box.startX,object.box.startY,5,0,2*Math.PI); + ctx.moveTo(object.box.endX,object.box.startY); + ctx.arc(object.box.endX,object.box.startY,5,0,2*Math.PI); + ctx.moveTo(object.box.endX,object.box.endY); + ctx.arc(object.box.endX,object.box.endY,5,0,2*Math.PI); + ctx.moveTo(object.box.startX,object.box.endY); + ctx.arc(object.box.startX,object.box.endY,5,0,2*Math.PI); ctx.fill(); - ctx.moveTo(object.startX,object.startY); - ctx.lineTo(object.endX,object.startY); - ctx.lineTo(object.endX,object.endY); - ctx.lineTo(object.startX,object.endY); - ctx.lineTo(object.startX,object.startY); + current_handles.tl = {} + ctx.moveTo(object.box.startX,object.box.startY); + ctx.lineTo(object.box.endX,object.box.startY); + ctx.lineTo(object.box.endX,object.box.endY); + ctx.lineTo(object.box.startX,object.box.endY); + ctx.lineTo(object.box.startX,object.box.startY); ctx.stroke() } function draw_outline(object){ @@ -560,41 +633,19 @@ function draw_outline(object){ ctx.restore(); } -function move(disp_x,disp_y){ - let arr = JSON.parse(localStorage.getItem("undo_stack")); - let object = arr[selected_index]; - if(object.type === "pencil" || object.type === "polygon"){ - for(let i = 0 ; i < object.pencil_array.length ; i++){ - object.pencil_array[i].x += disp_x; - object.pencil_array[i].y += disp_y; - } + +//select func: +function OOB(e){ + if(is_selected === 0){ + const x = e.clientX; + const y = e.clientY; + for(let i = state_array.length - 1 ; i >= 0 ; i--){ + if(ctx2.isPointInPath(state_array[i],x,y) || ctx2.isPointInStroke(state_array[i],x,y)){ + draw_outline(JSON.parse(localStorage.getItem("undo_stack"))[i]); + return; + } } - else{ - object.startX += disp_x; - object.endX += disp_x; - object.endY += disp_y; - object.startY += disp_y; } - arr[selected_index] = object; - state_array[selected_index] = convert_to_path2d(object); - ctx2.clearRect(0,0,canvas2.width,canvas2.height); - localStorage.setItem("undo_stack",JSON.stringify(arr)); - rerender(); - draw_outline(object); -} -//select func: -function select(event){ - const x = event.clientX; - const y = event.clientY; - for(let i = state_array.length - 1 ; i >= 0 ; i--){ - if(ctx2.isPointInPath(state_array[i],x,y) || ctx2.isPointInStroke(state_array[i],x,y)){ - selected_index = i; - canvas.classList.add("grabbing"); - return; - } - } - selected_index = -1; - canvas.classList.remove("grabbing"); } //----------------------------------------------------------------------------------------- //event listeners @@ -606,15 +657,7 @@ canvas.addEventListener("mousedown", (event) => { pencil_array = [{x: startX, y: startY}]; } else if(curr_tool === "selection"){ - if(selected_index !== -1){ - move_mode = 1; - startX = event.clientX; - startY = event.clientY; - draw_outline(JSON.parse(localStorage.getItem("undo_stack"))[selected_index]); - } - else{ - ctx.clearRect(0,0,canvas.width,canvas.height); - } + } else if(curr_tool === "text"){ if(text_box === 0){ From 96ee885619c9952a005eb2eb63e9c7886937ea17 Mon Sep 17 00:00:00 2001 From: xylo Date: Mon, 30 Mar 2026 19:45:24 +0530 Subject: [PATCH 27/33] feature : Add rotate,resize and move --- apollo.css | 1 - canvas.js | 511 ++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 368 insertions(+), 144 deletions(-) diff --git a/apollo.css b/apollo.css index 58a40ea..0c13cf2 100644 --- a/apollo.css +++ b/apollo.css @@ -32,7 +32,6 @@ button { align-items: center; justify-content: center; } - .crosshair { cursor : crosshair; } diff --git a/canvas.js b/canvas.js index d7336d6..48dde9c 100644 --- a/canvas.js +++ b/canvas.js @@ -19,40 +19,22 @@ let curr_tool = localStorage.getItem("curr_tool"); let stroke_style = localStorage.getItem("stroke_style"); let text_box = 0; let mouse_downed_text = 0; -console.log(localStorage.getItem("undo_stack")); let state_array = []; let move_mode = 0; let image_cache = {}; let is_selected = 0; let toolbar_state = 1; +let curr_cursor_class = "dummy_class"; //to speed up remove only this let current_handles = {tl:{},tr:{},bl:{},br:{},rot:{}}; +let mode = 'none'; +let curr_object = {}; +let s_index = -1; +let active_handle = 'none'; //mousedown -> draw dotted lines //----------------------------------------------------------------------------------------- //main funcs //convert to path2d: -function translater(x,y){ - let arr = JSON.parse(localStorage.getItem("undo_stack")); - let object = arr[selected_index]; - if(object.type === "pencil" || object.type === "polygon"){ - for(let i = 0 ; i < object.pencil_array.length ; i++){ - object.pencil_array[i].x += x; - object.pencil_array[i].y += y; - } - } - else{ - object.startX += x; - object.endX += x; - object.endY += y; - object.startY += y; - } - arr[selected_index] = object; - state_array[selected_index] = convert_to_path2d(object); - ctx2.clearRect(0,0,canvas2.width,canvas2.height); - localStorage.setItem("undo_stack",JSON.stringify(arr)); - rerender(); - draw_outline(object); -} function convert_to_path2d(object){ let path = new Path2D(); if(object.type === "line"){ @@ -63,13 +45,13 @@ function convert_to_path2d(object){ path.rect(object.startX,object.startY,object.endX-object.startX,object.endY-object.startY); } else if(object.type === "circle"){ - path.ellipse((object.startX+object.endX)/2, (object.endY+object.startY)/2,Math.abs((object.endX-object.startX)/2),Math.abs((object.endY-object.startY)/2),0,0,2*Math.PI) + path.ellipse((object.startX+object.endX)/2, (object.endY+object.startY)/2,(object.endX-object.startX)/2,(object.endY-object.startY)/2,0,0,2*Math.PI) } else if(object.type === "pencil" || object.type === "polygon") { - path.moveTo(object.centre.x + object.pencil_array[0].x, object.centre.y + object.pencil_array[0].y); + path.moveTo(object.pencil_array[0].x,object.pencil_array[0].y); for(let i = 1; i < object.pencil_array.length; i++){ - path.lineTo(object.centre.x + object.pencil_array[i].x,object.centre.y + object.pencil_array[i].y); + path.lineTo(object.pencil_array[i].x,object.pencil_array[i].y); } if(object.type === "polygon"){ path.closePath(); @@ -77,11 +59,20 @@ function convert_to_path2d(object){ } return path; } + +//to convert mouse coordinates to local coordinates relative to new axes: +function change_coordinates(X,Y,object){ + let a = X - object.centre.x; + let b = Y - object.centre.y; + const cosA = Math.cos(-object.angle); //rotate mouse coords opposite + const sinA = Math.sin(-object.angle); + return {x: a*cosA - b*sinA, y: a*sinA + b*cosA}; +} //canvas2 drawing function function canvas2draw(object) { ctx2.save(); - ctx2.setTransform(new DOMMatrix(object.transform)); ctx2.translate(object.centre.x,object.centre.y); + ctx2.rotate(object.angle); ctx2.strokeStyle = object.stroke_color; ctx2.globalAlpha = object.opacity; ctx2.lineDashOffset = 0; @@ -106,7 +97,7 @@ function canvas2draw(object) { } else if(object.type === "circle"){ ctx2.beginPath(); - ctx2.ellipse((object.startX+object.endX)/2, (object.endY+object.startY)/2,Math.abs((object.endX-object.startX)/2),Math.abs((object.endY-object.startY)/2),0,0,2*Math.PI); + ctx2.ellipse((object.startX+object.endX)/2, (object.endY+object.startY)/2,(object.endX-object.startX)/2,(object.endY-object.startY)/2,0,0,2*Math.PI); ctx2.stroke(); } else if(object.type === "rectangle"){ @@ -141,10 +132,10 @@ function canvas2draw(object) { ctx2.fillText(`${object.text}`,object.startX,object.startY); } else if(object.type === "image"){ - const x = Math.min(object.startX, object.endX); - const y = Math.min(object.startY, object.endY); - const w = Math.abs(object.endX - object.startX); - const h = Math.abs(object.endY - object.startY); + const x = object.startX; + const y = object.startY; + const w = (object.endX - object.startX); + const h = (object.endY - object.startY); if (image_cache[object.imgdata]) { ctx2.drawImage(image_cache[object.imgdata], x, y, w, h); } else { @@ -154,8 +145,8 @@ function canvas2draw(object) { image_cache[object.imgdata] = img; ctx2.save(); ctx2.globalAlpha = 1; - ctx2.setTransform(new DOMMatrix(object.transform)); ctx2.translate(object.centre.x,object.centre.y); + ctx2.rotate(object.angle); ctx2.drawImage(img, x, y, w, h); ctx2.restore(); @@ -165,14 +156,45 @@ function canvas2draw(object) { ctx2.restore(); } +//function to rerender the second canvas function rerender(){ + ctx2.save(); + ctx2.resetTransform(); + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + ctx2.restore(); const undo_stack = JSON.parse(localStorage.getItem("undo_stack")); for(let item of undo_stack){ - canvas2draw(item); + canvas2draw(item); }} //Object constructors +function update_handles(object){ + if(object.type !== "line"){ + object.tl_handle = {x: object.startX - 15, y: object.startY - 15}; + object.tr_handle = {x: object.endX + 15, y: object.startY - 15}; + object.br_handle = {x: object.endX + 15, y: object.endY + 15}; + object.bl_handle = {x: object.startX - 15, y: object.endY + 15}; + object.rotate_handle = { + x: (object.startX + object.endX) / 2, + y: object.startY - 45 + }; + } + else if(object.type === "line"){ + const sx = Math.min(object.startX, object.endX); + const ex = Math.max(object.endX, object.startX); + const sy = Math.min(object.startY,object.endY); + const ey = Math.max(object.endY,object.startY); + object.tl_handle = {x: sx - 15, y: sy - 15}; + object.tr_handle = {x: ex + 15, y: sy - 15}; + object.br_handle = {x: ex + 15, y: ey + 15}; + object.bl_handle = {x: sx - 15, y: ey + 15}; + object.rotate_handle = { + x: (sx + ex) / 2, + y: sy - 45 + }; + } +} +//function to initialize objects function createObject(e){ - const matrix = ctx.getTransform(); ctx.clearRect(0,0,canvas.width,canvas.height); is_drawing = false; if(curr_tool === "eraser"){ @@ -181,10 +203,13 @@ function createObject(e){ let object = {}; if (curr_tool === "image"){ let obj = new Shape_obj(e.clientX, e.clientY); - if (Math.abs(obj.endX - obj.startX) < 30) obj.endX = obj.startX + 30; - if (Math.abs(obj.endY - obj.startY) < 30) obj.endY = obj.startY + 30; const width = Math.max(30,Math.abs(obj.endX - obj.startX)); const height = Math.max(30,Math.abs(obj.endY - obj.startY)); + obj.startX = Math.min(obj.startX,obj.endX); + obj.startY = Math.min(obj.startY,obj.endY); + obj.endX = obj.startX + width; + obj.endY = obj.startY + height; + const img = new Image(); img.crossOrigin = "anonymous"; img.src = `https://picsum.photos/${width}/${height}`; @@ -201,18 +226,22 @@ function createObject(e){ let object = obj; object.type = "image"; object.imgdata = reader.result; - object.transform = matrix.toString(); - object.box = {}; - object.centre = {}; - object.box.startX = Math.min(object.startX, object.endX) - 15; - object.box.startY = Math.min(object.startY,object.endY) - 15; - object.box.endX = Math.max(object.endX,object.startX) + 15; - object.box.endY = Math.max(object.endY,object.startY) + 15; - object.centre = {x:(object.box.startX + object.box.endX)/2, y:(object.box.endY + object.box.startY)/2}; + object.angle = 0; + + object.centre = {x: (object.startX + object.endX)/2, y: (object.startY + object.endY)/2}; object.startX = object.startX - object.centre.x; object.startY = object.startY - object.centre.y; object.endX = object.endX - object.centre.x; object.endY = object.endY - object.centre.y; + + object.tl_handle = {x: object.startX - 15, y: object.startY - 15}; + object.tr_handle = {x: object.endX + 15, y: object.startY - 15}; + object.br_handle = {x: object.endX + 15, y: object.endY + 15}; + object.bl_handle = {x: object.startX - 15, y: object.endY + 15}; + object.rotate_handle = { + x: (object.startX + object.endX) / 2, + y: object.startY - 45 + }; let undo_stack = JSON.parse(localStorage.getItem("undo_stack")); undo_stack.push(object); localStorage.setItem("undo_stack", JSON.stringify(undo_stack)); @@ -273,43 +302,56 @@ function createObject(e){ object.startY = min_y; object.endY = max_y; } - object.transform = matrix.toString(); object.stroke_color = document.getElementById("stroke_color").value; object.opacity = document.getElementById("opacity").value/100; object.stroke_width = document.getElementById("stroke_width").value; object.stroke_style = stroke_style; - object.box = {}; - object.centre = {}; - object.box.startX = Math.min(object.startX, object.endX) - 15; - object.box.startY = Math.min(object.startY,object.endY) - 15; - object.box.endX = Math.max(object.endX,object.startX) + 15; - object.box.endY = Math.max(object.endY,object.startY) + 15; - object.box.rotate_handle_x = (object.box.startX + object.box.endX)/2; - object.box.rotate_handle_y = object.box.startY - 30; - object.centre = {x:(object.box.startX + object.box.endX)/2, y:(object.box.endY + object.box.startY)/2}; + object.angle = 0; + + object.centre = {x: (object.startX + object.endX)/2, y: (object.startY + object.endY)/2}; + object.startX = object.startX - object.centre.x; + object.startY = object.startY - object.centre.y; + object.endX = object.endX - object.centre.x; + object.endY = object.endY - object.centre.y; + update_handles(object); + if(object.type === "pencil" || object.type === "polygon"){ for(let i = 0 ; i < object.pencil_array.length; i++){ object.pencil_array[i].x -= object.centre.x; object.pencil_array[i].y -= object.centre.y; } } - object.startX = object.startX - object.centre.x; - object.startY = object.startY - object.centre.y; - object.endX = object.endX - object.centre.x; - object.endY = object.endY - object.centre.y; let undo_stack = JSON.parse(localStorage.getItem("undo_stack")); undo_stack.push(object); localStorage.setItem("undo_stack", JSON.stringify(undo_stack)); state_array.push(convert_to_path2d(object)); canvas2draw(object); } +//function to create corners function Shape_obj(endX,endY){ - this.startX = startX; - this.startY = startY; - this.endX = endX; - this.endY= endY; + if(curr_tool !== "line"){ + this.startX = Math.min(startX,endX); + this.startY = Math.min(startY,endY); + this.endX = Math.max(startX,endX); + this.endY= Math.max(startY,endY); + } + else if(curr_tool === "line"){ + this.startX = startX; + this.startY = startY; + this.endX = endX; + this.endY = endY; + } +} +//function to do the hittesting, x and y are mouse coordinates wr to og coords +function hit_test(index,obj,x,y){ + const p = change_coordinates(x,y,obj); + ctx2.save(); + ctx2.lineWidth = obj.stroke_width; + ctx2.resetTransform(); + const ans = (ctx2.isPointInPath(state_array[index],p.x,p.y) || ctx2.isPointInStroke(state_array[index],p.x,p.y)) ? 1 : 0; + ctx2.restore(); + return ans; } - //function to switch between font toolbar and shapes toolbar function change_toolbar(s){ if(toolbar_state === 1){ @@ -392,19 +434,19 @@ function add_element(s,startX,startY,endX,endY){ input.addEventListener("mousedown",(e)=>{e.stopPropagation()},true); } } -function erase(x,y){ +function erase(e){ + let arr = JSON.parse(localStorage.getItem("undo_stack")); for(let i = state_array.length - 1 ; i > -1;i--){ - if(ctx2.isPointInStroke(state_array[i],x,y) || ctx2.isPointInPath(state_array[i],x,y)){ - state_array.splice(i,1); - let arr = JSON.parse(localStorage.getItem("undo_stack")); - arr.splice(i,1); - localStorage.setItem("undo_stack",JSON.stringify(arr)); - ctx2.clearRect(0,0,canvas2.width,canvas2.height); - rerender(); + if(hit_test(i,arr[i],e.clientX,e.clientY) === 1){ + state_array.splice(i,1); + arr.splice(i,1); + localStorage.setItem("undo_stack",JSON.stringify(arr)); + } } - } + rerender(); } + //------------------------------Initializers----------------------------------------------- if(!localStorage.getItem("font_color")){ localStorage.setItem("font_color","#226e08"); @@ -578,74 +620,209 @@ function draw_free(endX, endY) { startX = endX; startY = endY; } +function return_box_outline(object){ + ctx2.save(); + ctx2.resetTransform(); + let path = new Path2D(); + ctx2.beginPath(); + path.moveTo(object.tl_handle.x, object.tl_handle.y); + path.arc(object.tl_handle.x,object.tl_handle.y,5,0,2*Math.PI); + path.moveTo(object.bl_handle.x,object.bl_handle.y); + path.arc(object.bl_handle.x,object.bl_handle.y,5,0,2*Math.PI); + path.moveTo(object.br_handle.x,object.br_handle.y); + path.arc(object.br_handle.x,object.br_handle.y,5,0,2*Math.PI); + path.moveTo(object.tr_handle.x,object.tr_handle.y); + path.arc(object.tr_handle.x,object.tr_handle.y,5,0,2*Math.PI); + path.moveTo(object.rotate_handle.x,object.rotate_handle.y); + path.arc(object.rotate_handle.x,object.rotate_handle.y,5,0,2*Math.PI); + path.moveTo(object.tl_handle.x,object.tl_handle.y); + path.lineTo(object.bl_handle.x,object.bl_handle.y); + path.lineTo(object.br_handle.x,object.br_handle.y); + path.lineTo(object.tr_handle.x,object.tr_handle.y); + path.closePath(); + ctx2.restore(); + return path; +} function draw_box_outline(object){ - ctx.beginPath(); - ctx.moveTo(object.box.rotate_handle_x, object.box.rotate_handle_y); - ctx.arc(object.box.rotate_handle_x,object.box.rotate_handle_y,5,0,2*Math.PI); - ctx.moveTo(object.box.startX,object.box.startY); - ctx.arc(object.box.startX,object.box.startY,5,0,2*Math.PI); - ctx.moveTo(object.box.endX,object.box.startY); - ctx.arc(object.box.endX,object.box.startY,5,0,2*Math.PI); - ctx.moveTo(object.box.endX,object.box.endY); - ctx.arc(object.box.endX,object.box.endY,5,0,2*Math.PI); - ctx.moveTo(object.box.startX,object.box.endY); - ctx.arc(object.box.startX,object.box.endY,5,0,2*Math.PI); - ctx.fill(); - current_handles.tl = {} - ctx.moveTo(object.box.startX,object.box.startY); - ctx.lineTo(object.box.endX,object.box.startY); - ctx.lineTo(object.box.endX,object.box.endY); - ctx.lineTo(object.box.startX,object.box.endY); - ctx.lineTo(object.box.startX,object.box.startY); - ctx.stroke() -} -function draw_outline(object){ - ctx.save(); - ctx.lineWidth = "2"; - ctx.lineJoin = "round"; - ctx.lineCap = "round"; - ctx.strokeStyle = "#16a7f5" - ctx.fillStyle = "#16a7f5" - ctx.globalAlpha = "1"; - ctx.setLineDash([]); - ctx.clearRect(0,0,canvas.width,canvas.height); - if(object.type === "pencil" || object.type === "polygon"){ - let max_x = object.pencil_array[0].x; - let min_x = object.pencil_array[0].x; - let max_y = object.pencil_array[0].y; - let min_y = object.pencil_array[0].y; - for(let i = 1 ; i < object.pencil_array.length; i++){ - max_x = Math.max(object.pencil_array[i].x,max_x); - max_y = Math.max(object.pencil_array[i].y,max_y); - min_x = Math.min(object.pencil_array[i].x,min_x); - min_y = Math.min(object.pencil_array[i].y,min_y); - } - object.startX = min_x; - object.endX = max_x; - object.startY = min_y; - object.endY = max_y; - draw_box_outline(object); - } - else{ - draw_box_outline(object); - } - - ctx.restore(); + ctx2.save(); + rerender(); + ctx2.resetTransform(); + ctx2.translate(object.centre.x,object.centre.y); + ctx2.rotate(object.angle); + ctx2.lineWidth = "2"; + ctx2.lineJoin = "round"; + ctx2.lineCap = "round"; + ctx2.strokeStyle = "#16a7f5" + ctx2.fillStyle = "#16a7f5" + ctx2.globalAlpha = "1"; + ctx2.setLineDash([]); + ctx2.beginPath(); + ctx2.moveTo(object.tl_handle.x, object.tl_handle.y); + ctx2.arc(object.tl_handle.x,object.tl_handle.y,5,0,2*Math.PI); + ctx2.moveTo(object.bl_handle.x,object.bl_handle.y); + ctx2.arc(object.bl_handle.x,object.bl_handle.y,5,0,2*Math.PI); + ctx2.moveTo(object.br_handle.x,object.br_handle.y); + ctx2.arc(object.br_handle.x,object.br_handle.y,5,0,2*Math.PI); + ctx2.moveTo(object.tr_handle.x,object.tr_handle.y); + ctx2.arc(object.tr_handle.x,object.tr_handle.y,5,0,2*Math.PI); + ctx2.moveTo(object.rotate_handle.x,object.rotate_handle.y); + ctx2.arc(object.rotate_handle.x,object.rotate_handle.y,5,0,2*Math.PI); + ctx2.fill(); + ctx2.moveTo(object.tl_handle.x,object.tl_handle.y); + ctx2.lineTo(object.bl_handle.x,object.bl_handle.y); + ctx2.lineTo(object.br_handle.x,object.br_handle.y); + ctx2.lineTo(object.tr_handle.x,object.tr_handle.y); + ctx2.closePath(); + ctx2.stroke(); + ctx2.restore(); } //select func: -function OOB(e){ +function OOB(e){ //takes event for coordinates and path2d object for restricting to one object + const x = e.clientX; + const y = e.clientY; if(is_selected === 0){ - const x = e.clientX; - const y = e.clientY; + const arr = JSON.parse(localStorage.getItem("undo_stack")) for(let i = state_array.length - 1 ; i >= 0 ; i--){ - if(ctx2.isPointInPath(state_array[i],x,y) || ctx2.isPointInStroke(state_array[i],x,y)){ - draw_outline(JSON.parse(localStorage.getItem("undo_stack"))[i]); - return; + if(hit_test(i,arr[i],x,y) === 1){ + s_index = i; + return 0; + } + } + return -1; + } + else { + const object = JSON.parse(localStorage.getItem("undo_stack"))[s_index]; + let ans = 0; + ctx2.save(); + ctx2.resetTransform(); + ctx2.lineWidth = object.stroke_width; + const pt = change_coordinates(x,y,object); + if(ctx2.isPointInPath(curr_object,pt.x,pt.y) || ctx2.isPointInStroke(curr_object,pt.x,pt.y)){ + if((Math.abs(pt.x - object.tl_handle.x) <= 11) && (Math.abs(pt.y - object.tl_handle.y) <= 11)){ + ans = 1; + active_handle = 0; + } + else if((Math.abs(pt.x - object.bl_handle.x) <= 11) && (Math.abs(pt.y - object.bl_handle.y) <= 11)){ + ans = 2; + active_handle = 1; + } + else if((Math.abs(pt.x - object.br_handle.x) <= 11) && (Math.abs(pt.y - object.br_handle.y) <= 11)){ + ans = 3; + active_handle = 2; + } + else if((Math.abs(pt.x - object.tr_handle.x) <= 11) && (Math.abs(pt.y - object.tr_handle.y) <= 11)){ + ans = 4; + active_handle = 3; + } + else if((Math.abs(pt.x - object.rotate_handle.x) <= 11) && (Math.abs(pt.y - object.rotate_handle.y) <= 11)){ + ans = 5; + } + else{ + ans = 0; + } + } + else{ + ans = -1; } + ctx2.restore(); + return ans; } +} +function cursor_setter(x){ + canvas.classList.remove(curr_cursor_class); //call with -1 to remove all curosrs + if(x !== -1){ + canvas.classList.add("grabbing"); + curr_cursor_class = "grabbing"; } + else{ + curr_cursor_class = "dummy_class" + } +} +function change_main_coords(mouse,handle,object){ + let arr = [object.tl_handle,object.bl_handle,object.br_handle,object.tr_handle]; + let scale = {x: 1,y: 1}; + if(Math.abs(mouse.x - arr[(handle+2)%4].x) <= 35 || Math.abs(arr[(handle+2)%4].y - mouse.y) <= 35 ){ + return scale; + } + else{ + arr[handle].x = mouse.x; + arr[handle].y = mouse.y; + if(handle % 2 === 0){ + arr[(handle + 1)%4].x = mouse.x; + arr[(handle + 3)%4].y = mouse.y; + } + else{ + arr[(handle + 1)%4].y = mouse.y; + arr[(handle + 3)%4].x = mouse.x; + } + } + scale.x = (Math.abs(arr[0].x - arr[2].x) - 30)/Math.abs(object.startX - object.endX); + scale.y = (Math.abs(arr[0].y - arr[1].y) - 30)/Math.abs(object.startY - object.endY); + const cd_x = (arr[0].x + arr[2].x)/2; + const cd_y = (arr[0].y + arr[1].y)/2; + const cosA = Math.cos(object.angle); + const sinA = Math.sin(object.angle); + object.centre.x = object.centre.x + cd_x*cosA - cd_y*sinA; + object.centre.y = object.centre.y + cd_y*cosA + cd_x*sinA; + for(let i = 0 ; i < 4 ; i++){ + arr[i].x -= cd_x; + arr[i].y -= cd_y; + } + object.rotate_handle.x = 0.5*(arr[0].x + arr[2].x); + object.rotate_handle.y = arr[0].y - 30; + return scale; +} +function move(e){ + let arr = JSON.parse(localStorage.getItem("undo_stack")); + let object = arr[s_index]; + object.centre.x += (e.clientX - startX); + object.centre.y += (e.clientY - startY); + arr[s_index] = object; + localStorage.setItem("undo_stack",JSON.stringify(arr)); + rerender(); + draw_box_outline(object); + return; +} + +function rotate(e){ + let arr = JSON.parse(localStorage.getItem("undo_stack")); + let object = arr[s_index]; + object.angle = Math.atan2(e.clientY - object.centre.y,e.clientX - object.centre.x) + 0.5*Math.PI; + arr[s_index] = object; + localStorage.setItem("undo_stack", JSON.stringify(arr)); + rerender(); + draw_box_outline(object); + return; +} + +function resize(e){ + let arr = JSON.parse(localStorage.getItem("undo_stack")); + let object = arr[s_index]; + let pt = change_coordinates(e.clientX,e.clientY,object); + const scale = change_main_coords(pt,active_handle,object); + const height = object.endY - object.startY; + object.startX *= scale.x; + object.startY *= scale.y; + object.endX *= scale.x; + object.endY *= scale.y; + if(object.type === "pencil" || object.type === "polygon"){ + for(let i = 0 ; i < object.pencil_array.length ; i++) + { + object.pencil_array[i].x *= scale.x; + object.pencil_array[i].y *= scale.y; + } + } + else if(object.type === "text"){ + object.font_size = (`${parseFloat(object.font_size) + object.endY - object.startY - height}px`); + } + arr[s_index] = object; + state_array[s_index] = convert_to_path2d(object); + localStorage.setItem("undo_stack", JSON.stringify(arr)); + rerender(); + draw_box_outline(object); + return; } //----------------------------------------------------------------------------------------- //event listeners @@ -657,7 +834,39 @@ canvas.addEventListener("mousedown", (event) => { pencil_array = [{x: startX, y: startY}]; } else if(curr_tool === "selection"){ - + if(is_selected === 0){ + const x = OOB(event); + if(x === 0){ + is_selected = 1; + curr_object = return_box_outline(JSON.parse(localStorage.getItem("undo_stack"))[s_index]); + draw_box_outline(JSON.parse(localStorage.getItem("undo_stack"))[s_index]); + } + } + else{ + const x = OOB(event); + startX = event.clientX; + startY = event.clientY; + if(x === -1){ + is_selected = 0; + curr_object = {}; + s_index = -1; + rerender(); + cursor_setter(-1); + mode = 'none'; + } + else if(x === 0){ + cursor_setter(0); + mode = "move"; + } + else if(x === 5){ + cursor_setter(5); + mode = "rotate"; + } + else{ + cursor_setter(x); + mode = "resize"; + } + } } else if(curr_tool === "text"){ if(text_box === 0){ @@ -738,7 +947,7 @@ canvas.addEventListener("mousemove", (event) => { } } else if(curr_tool === "eraser"){ - erase(event.clientX,event.clientY); + erase(event); } else if(curr_tool === "image"){ draw_rectangle(event.clientX,event.clientY); @@ -746,14 +955,23 @@ canvas.addEventListener("mousemove", (event) => { } if(curr_tool === "selection"){ - if(move_mode === 0){ - select(event); + if(is_selected === 1){ + if(mode === 'none'){ + const x = OOB(event); + cursor_setter(x); } - else{ - move(event.clientX - startX,event.clientY - startY); - startX = event.clientX; - startY = event.clientY; + else if(mode === 'rotate' ){ + rotate(event); + } + else if(mode === 'resize'){ + resize(event); + } + else if(mode === 'move'){ + move(event); } + } + startX = event.clientX; + startY = event.clientY; } }) @@ -770,7 +988,7 @@ canvas.addEventListener("mouseup", (e) => { text_box = 0; } else if(curr_tool === "selection"){ - move_mode = 0; + mode = 'none'; } else if(curr_tool !== "polygon"){ createObject(e); @@ -782,8 +1000,16 @@ canvas.addEventListener("mouseup", (e) => { //to prevent glitches when mouse leaves canvas canvas.addEventListener("mouseleave", (e) => { in_poly_mode = false; - move_mode = 0; - if(is_drawing === true){ + if(curr_tool === "selection"){ + is_selected = 0; + curr_object = {}; + s_index = -1; + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + rerender(); + cursor_setter(-1); + mode = 'none'; + } + else if(is_drawing === true){ createObject(e); } }) @@ -815,7 +1041,6 @@ function undo(e){ arr2.push(obj); localStorage.setItem("undo_stack", JSON.stringify(arr)); localStorage.setItem("redo_stack", JSON.stringify(arr2)); - ctx2.clearRect(0,0,canvas2.width,canvas2.height); rerender(); } } From 9f102d42fd3ca7325e4048282778347afac37781 Mon Sep 17 00:00:00 2001 From: xylo Date: Mon, 30 Mar 2026 21:17:46 +0530 Subject: [PATCH 28/33] feature : Support for mobile devices --- apollo.css | 1 + canvas.js | 50 +++++++++++++++++++++++++------------------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/apollo.css b/apollo.css index 0c13cf2..7e3ad6b 100644 --- a/apollo.css +++ b/apollo.css @@ -20,6 +20,7 @@ body { padding: 0; margin: 0 0 0 0; overflow: hidden; + touch-action: none; } button { background: none; diff --git a/canvas.js b/canvas.js index 48dde9c..6ca8363 100644 --- a/canvas.js +++ b/canvas.js @@ -18,7 +18,7 @@ let prev_undo = false; let curr_tool = localStorage.getItem("curr_tool"); let stroke_style = localStorage.getItem("stroke_style"); let text_box = 0; -let mouse_downed_text = 0; +let pointer_downed_text = 0; let state_array = []; let move_mode = 0; let image_cache = {}; @@ -30,7 +30,7 @@ let mode = 'none'; let curr_object = {}; let s_index = -1; let active_handle = 'none'; -//mousedown -> draw dotted lines +//pointerdown -> draw dotted lines //----------------------------------------------------------------------------------------- //main funcs @@ -60,11 +60,11 @@ function convert_to_path2d(object){ return path; } -//to convert mouse coordinates to local coordinates relative to new axes: +//to convert pointer coordinates to local coordinates relative to new axes: function change_coordinates(X,Y,object){ let a = X - object.centre.x; let b = Y - object.centre.y; - const cosA = Math.cos(-object.angle); //rotate mouse coords opposite + const cosA = Math.cos(-object.angle); //rotate pointer coords opposite const sinA = Math.sin(-object.angle); return {x: a*cosA - b*sinA, y: a*sinA + b*cosA}; } @@ -203,8 +203,8 @@ function createObject(e){ let object = {}; if (curr_tool === "image"){ let obj = new Shape_obj(e.clientX, e.clientY); - const width = Math.max(30,Math.abs(obj.endX - obj.startX)); - const height = Math.max(30,Math.abs(obj.endY - obj.startY)); + const width = Math.round(Math.max(30,Math.abs(obj.endX - obj.startX))); + const height = Math.round(Math.max(30,Math.abs(obj.endY - obj.startY))); obj.startX = Math.min(obj.startX,obj.endX); obj.startY = Math.min(obj.startY,obj.endY); obj.endX = obj.startX + width; @@ -342,7 +342,7 @@ function Shape_obj(endX,endY){ this.endY = endY; } } -//function to do the hittesting, x and y are mouse coordinates wr to og coords +//function to do the hittesting, x and y are pointer coordinates wr to og coords function hit_test(index,obj,x,y){ const p = change_coordinates(x,y,obj); ctx2.save(); @@ -431,7 +431,7 @@ function add_element(s,startX,startY,endX,endY){ input.style.width = `${input.scrollWidth}px`; }) input.focus(); - input.addEventListener("mousedown",(e)=>{e.stopPropagation()},true); + input.addEventListener("pointerdown",(e)=>{e.stopPropagation()},true); } } function erase(e){ @@ -740,22 +740,22 @@ function cursor_setter(x){ curr_cursor_class = "dummy_class" } } -function change_main_coords(mouse,handle,object){ +function change_main_coords(pointer,handle,object){ let arr = [object.tl_handle,object.bl_handle,object.br_handle,object.tr_handle]; let scale = {x: 1,y: 1}; - if(Math.abs(mouse.x - arr[(handle+2)%4].x) <= 35 || Math.abs(arr[(handle+2)%4].y - mouse.y) <= 35 ){ + if(Math.abs(pointer.x - arr[(handle+2)%4].x) <= 35 || Math.abs(arr[(handle+2)%4].y - pointer.y) <= 35 ){ return scale; } else{ - arr[handle].x = mouse.x; - arr[handle].y = mouse.y; + arr[handle].x = pointer.x; + arr[handle].y = pointer.y; if(handle % 2 === 0){ - arr[(handle + 1)%4].x = mouse.x; - arr[(handle + 3)%4].y = mouse.y; + arr[(handle + 1)%4].x = pointer.x; + arr[(handle + 3)%4].y = pointer.y; } else{ - arr[(handle + 1)%4].y = mouse.y; - arr[(handle + 3)%4].x = mouse.x; + arr[(handle + 1)%4].y = pointer.y; + arr[(handle + 3)%4].x = pointer.x; } } scale.x = (Math.abs(arr[0].x - arr[2].x) - 30)/Math.abs(object.startX - object.endX); @@ -826,7 +826,7 @@ function resize(e){ } //----------------------------------------------------------------------------------------- //event listeners -canvas.addEventListener("mousedown", (event) => { +canvas.addEventListener("pointerdown", (event) => { is_drawing = true; startX = event.clientX; startY = event.clientY; @@ -870,7 +870,7 @@ canvas.addEventListener("mousedown", (event) => { } else if(curr_tool === "text"){ if(text_box === 0){ - mouse_downed_text = 1; + pointer_downed_text = 1; ctx.lineWidth = 1; if(localStorage.getItem('light') === 'true'){ ctx.strokeStyle = "black"; @@ -881,7 +881,7 @@ canvas.addEventListener("mousedown", (event) => { } } }) -document.body.addEventListener("mousedown" , (e) => { +document.body.addEventListener("pointerdown" , (e) => { if(curr_tool === "text" && text_box === 1){ if(document.getElementById("textbox").value !== null){createObject(e);} else{document.body.removeChild(document.getElementById("textbox")); @@ -921,7 +921,7 @@ canvas.addEventListener("dblclick", (e) => { createObject(e) } }) -canvas.addEventListener("mousemove", (event) => { +canvas.addEventListener("pointermove", (event) => { if (is_drawing === true) { prev_undo = false; localStorage.setItem("redo_stack","[]"); @@ -942,7 +942,7 @@ canvas.addEventListener("mousemove", (event) => { draw_line(event.clientX, event.clientY); } else if(curr_tool === "text"){ - if(text_box === 0 && mouse_downed_text === 1){ + if(text_box === 0 && pointer_downed_text === 1){ draw_rectangle(event.clientX, event.clientY); } } @@ -975,14 +975,14 @@ canvas.addEventListener("mousemove", (event) => { } }) -canvas.addEventListener("mouseup", (e) => { +canvas.addEventListener("pointerup", (e) => { if(curr_tool === "text" && text_box === 0){ ctx.lineWidth = localStorage.getItem("stroke_width"); ctx.strokeStyle = localStorage.getItem("stroke_color"); add_element("text",startX,startY,e.clientX,e.clientY); is_drawing = false; text_box = 1; - mouse_downed_text = 0; + pointer_downed_text = 0; } else if(curr_tool === "text" && text_box === 1){ text_box = 0; @@ -997,8 +997,8 @@ canvas.addEventListener("mouseup", (e) => { }); -//to prevent glitches when mouse leaves canvas -canvas.addEventListener("mouseleave", (e) => { +//to prevent glitches when pointer leaves canvas +canvas.addEventListener("pointerleave", (e) => { in_poly_mode = false; if(curr_tool === "selection"){ is_selected = 0; From 1c3622e2cdb57de387c5d36c30067a70cc67d9ea Mon Sep 17 00:00:00 2001 From: xylo Date: Mon, 30 Mar 2026 22:17:51 +0530 Subject: [PATCH 29/33] bugfix: phone touch events not working properly --- apollo.css | 3 ++- canvas.js | 54 +++++++++++++++++++++++++++++++++---------------- toggle_theme.js | 1 + 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/apollo.css b/apollo.css index 7e3ad6b..8f42d89 100644 --- a/apollo.css +++ b/apollo.css @@ -275,7 +275,8 @@ output{ width:90vw; left:5vw; height:6vh; - right:5vw + right:5vw; + padding : 0 1vw; } #theme{ top:auto; diff --git a/canvas.js b/canvas.js index 6ca8363..c6b1782 100644 --- a/canvas.js +++ b/canvas.js @@ -888,6 +888,43 @@ document.body.addEventListener("pointerdown" , (e) => { } } },false) +let lastClick = 0 +canvas.addEventListener("touchstart", (e) => { + let date = new Date(); + let time = date.getTime(); + const time_between_taps = 400; // 200ms + if (time - lastClick > time_between_taps) { + if(curr_tool === "polygon"){ + is_drawing = true; + startX = e.clientX; + startY = e.clientY; + if(in_poly_mode === false){ + in_poly_mode = true; + pencil_array = [{x: startX, y: startY}]; + } + else{ + ctx.clearRect(0,0,canvas.width,canvas.height); + pencil_array.push({x: startX, y: startY}); + ctx2.beginPath(); + ctx2.moveTo(startX,startY); + ctx2.lineTo(pencil_array[pencil_array.length-2].x,pencil_array[pencil_array.length-2].y); + ctx2.stroke(); + } + } + } + else{ + startX = e.clientX; + startY = e.clientY; + in_poly_mode = false; + pencil_array.push({x: startX, y: startY}); + ctx.beginPath(); + ctx.moveTo(startX,startY); + ctx.lineTo(pencil_array[0].x,pencil_array[0].y); + ctx.stroke(); + createObject(e) + } + lastClick = time; +}) canvas.addEventListener("click", (event) => { if(curr_tool === "polygon"){ @@ -997,23 +1034,6 @@ canvas.addEventListener("pointerup", (e) => { }); -//to prevent glitches when pointer leaves canvas -canvas.addEventListener("pointerleave", (e) => { - in_poly_mode = false; - if(curr_tool === "selection"){ - is_selected = 0; - curr_object = {}; - s_index = -1; - ctx2.clearRect(0,0,canvas2.width,canvas2.height); - rerender(); - cursor_setter(-1); - mode = 'none'; - } - else if(is_drawing === true){ - createObject(e); - } - }) - document.getElementById("stroke_color").addEventListener("change", (event) => { localStorage.setItem("stroke_color", event.currentTarget.value); ctx.strokeStyle = event.currentTarget.value; diff --git a/toggle_theme.js b/toggle_theme.js index 3986e2f..9f1bbd2 100644 --- a/toggle_theme.js +++ b/toggle_theme.js @@ -4,6 +4,7 @@ if(!localStorage.getItem("canvas_color")){ localStorage.setItem("canvas_color","#222831") } document.documentElement.style.setProperty('--bg-color', `${localStorage.getItem("canvas_color")}`); +document.getElementById("canvas_color").value = localStorage.getItem("canvas_color"); document.getElementById("canvas_color").addEventListener("change",(e)=>{ localStorage.setItem("canvas_color",`${e.currentTarget.value}`) document.documentElement.style.setProperty('--bg-color', `${e.currentTarget.value}`); From 28083d03f6a90571009c12136af5b2822ca7ab6f Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 31 Mar 2026 00:34:11 +0530 Subject: [PATCH 30/33] revert last commit --- canvas.js | 56 ++++++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/canvas.js b/canvas.js index c6b1782..1dce514 100644 --- a/canvas.js +++ b/canvas.js @@ -888,43 +888,6 @@ document.body.addEventListener("pointerdown" , (e) => { } } },false) -let lastClick = 0 -canvas.addEventListener("touchstart", (e) => { - let date = new Date(); - let time = date.getTime(); - const time_between_taps = 400; // 200ms - if (time - lastClick > time_between_taps) { - if(curr_tool === "polygon"){ - is_drawing = true; - startX = e.clientX; - startY = e.clientY; - if(in_poly_mode === false){ - in_poly_mode = true; - pencil_array = [{x: startX, y: startY}]; - } - else{ - ctx.clearRect(0,0,canvas.width,canvas.height); - pencil_array.push({x: startX, y: startY}); - ctx2.beginPath(); - ctx2.moveTo(startX,startY); - ctx2.lineTo(pencil_array[pencil_array.length-2].x,pencil_array[pencil_array.length-2].y); - ctx2.stroke(); - } - } - } - else{ - startX = e.clientX; - startY = e.clientY; - in_poly_mode = false; - pencil_array.push({x: startX, y: startY}); - ctx.beginPath(); - ctx.moveTo(startX,startY); - ctx.lineTo(pencil_array[0].x,pencil_array[0].y); - ctx.stroke(); - createObject(e) - } - lastClick = time; -}) canvas.addEventListener("click", (event) => { if(curr_tool === "polygon"){ @@ -1034,6 +997,23 @@ canvas.addEventListener("pointerup", (e) => { }); +//to prevent glitches when pointer leaves canvas +canvas.addEventListener("pointerleave", (e) => { + in_poly_mode = false; + if(curr_tool === "selection"){ + is_selected = 0; + curr_object = {}; + s_index = -1; + ctx2.clearRect(0,0,canvas2.width,canvas2.height); + rerender(); + cursor_setter(-1); + mode = 'none'; + } + else if(is_drawing === true){ + createObject(e); + } + }) + document.getElementById("stroke_color").addEventListener("change", (event) => { localStorage.setItem("stroke_color", event.currentTarget.value); ctx.strokeStyle = event.currentTarget.value; @@ -1104,4 +1084,4 @@ document.getElementById("opacity_value").textContent = `${event.currentTarget.va document.getElementById("stroke_width_value").textContent = `${document.getElementById("stroke_width").value}px`; document.getElementById("stroke_width").addEventListener("input", (event) => { document.getElementById("stroke_width_value").textContent = `${event.currentTarget.value}px`; -}); +}); \ No newline at end of file From eabd6fa9273bb616565a7e18f23895c6ef0e5bae Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 31 Mar 2026 01:05:29 +0530 Subject: [PATCH 31/33] bugfix: fix bugs in select tool --- apollo.css | 1 - canvas.js | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apollo.css b/apollo.css index 8f42d89..5de69a0 100644 --- a/apollo.css +++ b/apollo.css @@ -20,7 +20,6 @@ body { padding: 0; margin: 0 0 0 0; overflow: hidden; - touch-action: none; } button { background: none; diff --git a/canvas.js b/canvas.js index 1dce514..3251e18 100644 --- a/canvas.js +++ b/canvas.js @@ -827,6 +827,7 @@ function resize(e){ //----------------------------------------------------------------------------------------- //event listeners canvas.addEventListener("pointerdown", (event) => { + event.preventDefault(); is_drawing = true; startX = event.clientX; startY = event.clientY; @@ -834,6 +835,7 @@ canvas.addEventListener("pointerdown", (event) => { pencil_array = [{x: startX, y: startY}]; } else if(curr_tool === "selection"){ + is_drawing = false; if(is_selected === 0){ const x = OOB(event); if(x === 0){ @@ -882,6 +884,7 @@ canvas.addEventListener("pointerdown", (event) => { } }) document.body.addEventListener("pointerdown" , (e) => { + e.preventDefault(); if(curr_tool === "text" && text_box === 1){ if(document.getElementById("textbox").value !== null){createObject(e);} else{document.body.removeChild(document.getElementById("textbox")); @@ -889,7 +892,8 @@ document.body.addEventListener("pointerdown" , (e) => { } },false) -canvas.addEventListener("click", (event) => { +canvas.addEventListener("click", (event) => { + event.preventDefault(); if(curr_tool === "polygon"){ is_drawing = true; startX = event.clientX; @@ -909,6 +913,7 @@ canvas.addEventListener("click", (event) => { } }) canvas.addEventListener("dblclick", (e) => { + e.preventDefault(); if(curr_tool === "polygon"){ startX = e.clientX; startY = e.clientY; @@ -922,6 +927,7 @@ canvas.addEventListener("dblclick", (e) => { } }) canvas.addEventListener("pointermove", (event) => { + event.preventDefault(); if (is_drawing === true) { prev_undo = false; localStorage.setItem("redo_stack","[]"); @@ -976,6 +982,7 @@ canvas.addEventListener("pointermove", (event) => { } }) canvas.addEventListener("pointerup", (e) => { + e.preventDefault(); if(curr_tool === "text" && text_box === 0){ ctx.lineWidth = localStorage.getItem("stroke_width"); ctx.strokeStyle = localStorage.getItem("stroke_color"); @@ -999,6 +1006,7 @@ canvas.addEventListener("pointerup", (e) => { //to prevent glitches when pointer leaves canvas canvas.addEventListener("pointerleave", (e) => { + e.preventDefault(); in_poly_mode = false; if(curr_tool === "selection"){ is_selected = 0; From 9cd38083faecef9f915ab8e386973ae5196d67ea Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 31 Mar 2026 01:23:27 +0530 Subject: [PATCH 32/33] bugfix: objects appear out of place --- apollo.css | 4 ++++ canvas.js | 11 ++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/apollo.css b/apollo.css index 5de69a0..e8d747a 100644 --- a/apollo.css +++ b/apollo.css @@ -197,6 +197,10 @@ button { left:0; } +#test, #final{ + touch-action: none; +} + .beautifiers{ border:0; margin:0; diff --git a/canvas.js b/canvas.js index 3251e18..4207c12 100644 --- a/canvas.js +++ b/canvas.js @@ -827,7 +827,6 @@ function resize(e){ //----------------------------------------------------------------------------------------- //event listeners canvas.addEventListener("pointerdown", (event) => { - event.preventDefault(); is_drawing = true; startX = event.clientX; startY = event.clientY; @@ -835,8 +834,8 @@ canvas.addEventListener("pointerdown", (event) => { pencil_array = [{x: startX, y: startY}]; } else if(curr_tool === "selection"){ - is_drawing = false; if(is_selected === 0){ + is_drawing = false; const x = OOB(event); if(x === 0){ is_selected = 1; @@ -884,7 +883,6 @@ canvas.addEventListener("pointerdown", (event) => { } }) document.body.addEventListener("pointerdown" , (e) => { - e.preventDefault(); if(curr_tool === "text" && text_box === 1){ if(document.getElementById("textbox").value !== null){createObject(e);} else{document.body.removeChild(document.getElementById("textbox")); @@ -892,8 +890,7 @@ document.body.addEventListener("pointerdown" , (e) => { } },false) -canvas.addEventListener("click", (event) => { - event.preventDefault(); +canvas.addEventListener("click", (event) => { if(curr_tool === "polygon"){ is_drawing = true; startX = event.clientX; @@ -913,7 +910,6 @@ canvas.addEventListener("click", (event) => { } }) canvas.addEventListener("dblclick", (e) => { - e.preventDefault(); if(curr_tool === "polygon"){ startX = e.clientX; startY = e.clientY; @@ -927,7 +923,6 @@ canvas.addEventListener("dblclick", (e) => { } }) canvas.addEventListener("pointermove", (event) => { - event.preventDefault(); if (is_drawing === true) { prev_undo = false; localStorage.setItem("redo_stack","[]"); @@ -982,7 +977,6 @@ canvas.addEventListener("pointermove", (event) => { } }) canvas.addEventListener("pointerup", (e) => { - e.preventDefault(); if(curr_tool === "text" && text_box === 0){ ctx.lineWidth = localStorage.getItem("stroke_width"); ctx.strokeStyle = localStorage.getItem("stroke_color"); @@ -1006,7 +1000,6 @@ canvas.addEventListener("pointerup", (e) => { //to prevent glitches when pointer leaves canvas canvas.addEventListener("pointerleave", (e) => { - e.preventDefault(); in_poly_mode = false; if(curr_tool === "selection"){ is_selected = 0; From 03b9463a0ea2af0325e2a8b7935ce3b4555db586 Mon Sep 17 00:00:00 2001 From: xylo Date: Tue, 31 Mar 2026 10:07:21 +0530 Subject: [PATCH 33/33] feature : enhance selection tool functionality on phone --- canvas.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/canvas.js b/canvas.js index 4207c12..327d37e 100644 --- a/canvas.js +++ b/canvas.js @@ -1000,6 +1000,9 @@ canvas.addEventListener("pointerup", (e) => { //to prevent glitches when pointer leaves canvas canvas.addEventListener("pointerleave", (e) => { + if(e.pointerType !== "mouse"){ + return; + } in_poly_mode = false; if(curr_tool === "selection"){ is_selected = 0;