canvas.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. const canvas = document.querySelector(".canvas canvas");
  2. const ctx = canvas.getContext("2d", { willReadFrequently: true });
  3. ctx.imageSmoothingEnabled = false;
  4. let drawing = false;
  5. let lastX, lastY;
  6. let strokeSize = 8;
  7. let strokeColor = "black";
  8. function drawPixelLine(x0, y0, x1, y1) {
  9. const dx = Math.abs(x1 - x0);
  10. const dy = Math.abs(y1 - y0);
  11. const sx = x0 < x1 ? 1 : -1;
  12. const sy = y0 < y1 ? 1 : -1;
  13. let err = dx - dy;
  14. while (true) {
  15. ctx.fillRect(x0, y0, strokeSize, strokeSize);
  16. if (x0 === x1 && y0 === y1) break;
  17. const e2 = 2 * err;
  18. if (e2 > -dy) {
  19. err -= dy;
  20. x0 += sx;
  21. }
  22. if (e2 < dx) {
  23. err += dx;
  24. y0 += sy;
  25. }
  26. }
  27. }
  28. // Disable dragging for all images on the page
  29. document.querySelectorAll('img').forEach(img => {
  30. img.addEventListener('dragstart', (e) => e.preventDefault());
  31. });
  32. canvas.addEventListener("mousedown", start);
  33. canvas.addEventListener("mouseup", () => (drawing = false));
  34. canvas.addEventListener("mouseleave", () => (drawing = false));
  35. canvas.addEventListener("mousemove", move);
  36. function start(e) {
  37. drawing = true;
  38. const rect = canvas.getBoundingClientRect();
  39. const scaleX = canvas.width / rect.width;
  40. const scaleY = canvas.height / rect.height;
  41. lastX = Math.floor((e.clientX - rect.left) * scaleX);
  42. lastY = Math.floor((e.clientY - rect.top) * scaleY);
  43. }
  44. function move(e) {
  45. if (!drawing) return;
  46. const rect = canvas.getBoundingClientRect();
  47. const scaleX = canvas.width / rect.width;
  48. const scaleY = canvas.height / rect.height;
  49. const x = Math.floor((e.clientX - rect.left) * scaleX);
  50. const y = Math.floor((e.clientY - rect.top) * scaleY);
  51. ctx.fillStyle = strokeColor;
  52. drawPixelLine(lastX, lastY, x, y);
  53. lastX = x;
  54. lastY = y;
  55. }
  56. canvas.addEventListener("touchstart", (e) => {
  57. e.preventDefault();
  58. const touch = e.touches[0];
  59. start(touch);
  60. });
  61. canvas.addEventListener("touchmove", (e) => {
  62. e.preventDefault();
  63. const touch = e.touches[0];
  64. move(touch);
  65. });
  66. canvas.addEventListener("touchend", (e) => {
  67. e.preventDefault();
  68. drawing = false;
  69. });
  70. /* BUTTONS */
  71. let pencilButton = document.querySelector("button#pencil");
  72. let eraserButton = document.querySelector("button#eraser");
  73. let smallButton = document.querySelector("button#small");
  74. let mediumButton = document.querySelector("button#medium");
  75. let clearButton = document.querySelector("button#clear");
  76. pencilButton.addEventListener("click", (e) => {
  77. e.preventDefault();
  78. strokeColor = "black";
  79. pencilButton.classList.add("active-tool");
  80. eraserButton.classList.remove("active-tool");
  81. });
  82. eraserButton.addEventListener("click", (e) => {
  83. e.preventDefault();
  84. strokeColor = "white";
  85. eraserButton.classList.add("active-tool");
  86. pencilButton.classList.remove("active-tool");
  87. });
  88. smallButton.addEventListener("click", (e) => {
  89. e.preventDefault();
  90. strokeSize = 4;
  91. smallButton.classList.add("active-tool");
  92. mediumButton.classList.remove("active-tool");
  93. });
  94. mediumButton.addEventListener("click", (e) => {
  95. e.preventDefault();
  96. strokeSize = 8;
  97. mediumButton.classList.add("active-tool");
  98. smallButton.classList.remove("active-tool");
  99. });
  100. clearButton.addEventListener("click", (e) => {
  101. e.preventDefault();
  102. ctx.clearRect(0, 0, canvas.width, canvas.height);
  103. window.clearCurrentText();
  104. });
  105. copyButton = document.querySelector("button#copy");
  106. copyButton.addEventListener("click", (e) => {
  107. e.preventDefault();
  108. // copy image
  109. const images = document.querySelectorAll(
  110. "#chat-log .message-box .canvas img"
  111. );
  112. const lastImage = images[0];
  113. if (lastImage) {
  114. const img = new Image();
  115. img.onload = function () {
  116. ctx.clearRect(0, 0, canvas.width, canvas.height);
  117. ctx.drawImage(img, 0, 0);
  118. };
  119. img.src = lastImage.src;
  120. }
  121. // copy text
  122. const texts = document.querySelectorAll(
  123. "#chat-log .message-box .canvas .message-text"
  124. );
  125. const lastText = texts[0];
  126. if (lastText) {
  127. const text = lastText.innerHTML.replace(/<br>/g, "\n");
  128. window.setCurrentText(text);
  129. }
  130. });
  131. // Color swaths
  132. const swaths = document.querySelectorAll(".swatch");
  133. swaths.forEach(swatch => {
  134. swatch.addEventListener("click", (e) => {
  135. e.preventDefault();
  136. const color = swatch.getAttribute("data-color");
  137. strokeColor = color;
  138. swaths.forEach(s => s.classList.remove("active-swatch"));
  139. swatch.classList.add("active-swatch");
  140. // Update user's theme color to match
  141. if (window.displayUserColor) {
  142. window.userColor = color;
  143. window.displayUserColor(color);
  144. }
  145. // Unset eraser if active
  146. pencilButton.classList.add("active-tool");
  147. eraserButton.classList.remove("active-tool");
  148. });
  149. });
  150. // Sync custom color picker with stroke color
  151. const picker = document.getElementById("user-color-picker");
  152. if (picker) {
  153. picker.addEventListener("input", (e) => {
  154. strokeColor = e.target.value;
  155. swaths.forEach(s => s.classList.remove("active-swatch"));
  156. // We could add an active class to the picker container but it's okay
  157. pencilButton.classList.add("active-tool");
  158. eraserButton.classList.remove("active-tool");
  159. });
  160. }
  161. // Expand Button
  162. const expandBtn = document.getElementById("expand-btn");
  163. if (expandBtn) {
  164. expandBtn.addEventListener("click", (e) => {
  165. e.preventDefault();
  166. document.body.classList.toggle("fullscreen-mode");
  167. if (document.body.classList.contains("fullscreen-mode")) {
  168. expandBtn.innerHTML = "shrink ⤡";
  169. } else {
  170. expandBtn.innerHTML = "expand ⤢";
  171. }
  172. });
  173. }
  174. // Randomize Color Button
  175. const randomColorBtn = document.getElementById("random-color-btn");
  176. if (randomColorBtn) {
  177. randomColorBtn.addEventListener("click", (e) => {
  178. e.preventDefault();
  179. const newColor = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
  180. strokeColor = newColor;
  181. swaths.forEach(s => s.classList.remove("active-swatch"));
  182. if (picker) picker.value = newColor;
  183. if (window.displayUserColor) {
  184. window.userColor = newColor;
  185. window.displayUserColor(newColor);
  186. }
  187. pencilButton.classList.add("active-tool");
  188. eraserButton.classList.remove("active-tool");
  189. });
  190. }
  191. // Theme Toggle Button
  192. const themeBtn = document.getElementById("theme-btn");
  193. if (themeBtn) {
  194. themeBtn.addEventListener("click", (e) => {
  195. e.preventDefault();
  196. document.body.classList.toggle("light-mode");
  197. if (document.body.classList.contains("light-mode")) {
  198. themeBtn.innerHTML = "dark mode 🌙";
  199. } else {
  200. themeBtn.innerHTML = "light mode ☀";
  201. }
  202. });
  203. }
  204. // Download Archive Button
  205. const downloadBtn = document.getElementById("download-btn");
  206. if (downloadBtn) {
  207. downloadBtn.addEventListener("click", (e) => {
  208. e.preventDefault();
  209. window.location.href = "/api/download_images";
  210. });
  211. }