drawing.component.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import { Component, OnInit, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
  2. import { IonicModule } from '@ionic/angular';
  3. import { CommonModule } from '@angular/common';
  4. import { FormsModule } from '@angular/forms';
  5. @Component({
  6. selector: 'app-drawing',
  7. templateUrl: './drawing.component.html',
  8. styleUrls: ['./drawing.component.scss'],
  9. standalone: true,
  10. imports: [IonicModule, CommonModule, FormsModule],
  11. })
  12. export class DrawingComponent implements OnInit, AfterViewInit {
  13. @ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
  14. private ctx!: CanvasRenderingContext2D;
  15. private isDrawing = false;
  16. private lastX = 0;
  17. private lastY = 0;
  18. currentColor = '#000000';
  19. currentWidth = 5;
  20. customColor = '#000000';
  21. showGrid = false;
  22. opacity = 1;
  23. smoothing = true;
  24. showLayers = false;
  25. currentLayer = 'layer1';
  26. layers = [
  27. { id: 'layer1', name: '图层 1', visible: true },
  28. { id: 'layer2', name: '图层 2', visible: true }
  29. ];
  30. history: ImageData[] = [];
  31. historyIndex = -1;
  32. tools = [
  33. { id: 'brush', icon: 'brush', name: '画笔' },
  34. { id: 'eraser', icon: 'trash', name: '橡皮擦' },
  35. { id: 'line', icon: 'remove', name: '直线' },
  36. { id: 'rect', icon: 'square-outline', name: '矩形' },
  37. { id: 'circle', icon: 'ellipse-outline', name: '圆形' },
  38. { id: 'clear', icon: 'refresh', name: '清空' }
  39. ];
  40. colors = [
  41. '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff',
  42. '#ffff00', '#00ffff', '#ff00ff', '#808080', '#800000'
  43. ];
  44. selectedTool = 'brush';
  45. constructor() { }
  46. ngOnInit() { }
  47. ngAfterViewInit() {
  48. const canvas = this.canvasRef.nativeElement;
  49. this.ctx = canvas.getContext('2d')!;
  50. this.resizeCanvas();
  51. this.initializeCanvas();
  52. window.addEventListener('resize', () => {
  53. this.resizeCanvas();
  54. });
  55. }
  56. private resizeCanvas() {
  57. const canvas = this.canvasRef.nativeElement;
  58. canvas.width = window.innerWidth;
  59. canvas.height = window.innerHeight - 200; // 减去顶部工具栏的高度
  60. }
  61. private initializeCanvas() {
  62. this.ctx.fillStyle = '#ffffff';
  63. this.ctx.fillRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height);
  64. }
  65. startDrawing(event: TouchEvent | MouseEvent) {
  66. this.isDrawing = true;
  67. const pos = this.getPosition(event);
  68. this.lastX = pos.x;
  69. this.lastY = pos.y;
  70. }
  71. draw(event: TouchEvent | MouseEvent) {
  72. if (!this.isDrawing) return;
  73. event.preventDefault();
  74. const pos = this.getPosition(event);
  75. switch (this.selectedTool) {
  76. case 'brush':
  77. case 'eraser':
  78. this.drawFreehand(pos);
  79. break;
  80. case 'line':
  81. this.drawLine(pos);
  82. break;
  83. case 'rect':
  84. this.drawRect(pos);
  85. break;
  86. case 'circle':
  87. this.drawCircle(pos);
  88. break;
  89. }
  90. }
  91. private drawFreehand(pos: { x: number, y: number }) {
  92. this.ctx.beginPath();
  93. this.ctx.moveTo(this.lastX, this.lastY);
  94. this.ctx.lineTo(pos.x, pos.y);
  95. this.ctx.strokeStyle = this.selectedTool === 'eraser' ? '#ffffff' : this.currentColor;
  96. this.ctx.lineWidth = this.currentWidth;
  97. this.ctx.lineCap = 'round';
  98. this.ctx.stroke();
  99. this.lastX = pos.x;
  100. this.lastY = pos.y;
  101. }
  102. private drawLine(pos: { x: number, y: number }) {
  103. const tempCanvas = document.createElement('canvas');
  104. tempCanvas.width = this.canvasRef.nativeElement.width;
  105. tempCanvas.height = this.canvasRef.nativeElement.height;
  106. const tempCtx = tempCanvas.getContext('2d')!;
  107. tempCtx.drawImage(this.canvasRef.nativeElement, 0, 0);
  108. tempCtx.beginPath();
  109. tempCtx.moveTo(this.lastX, this.lastY);
  110. tempCtx.lineTo(pos.x, pos.y);
  111. tempCtx.strokeStyle = this.currentColor;
  112. tempCtx.lineWidth = this.currentWidth;
  113. tempCtx.stroke();
  114. this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height);
  115. this.ctx.drawImage(tempCanvas, 0, 0);
  116. }
  117. private drawRect(pos: { x: number, y: number }) {
  118. const tempCanvas = document.createElement('canvas');
  119. tempCanvas.width = this.canvasRef.nativeElement.width;
  120. tempCanvas.height = this.canvasRef.nativeElement.height;
  121. const tempCtx = tempCanvas.getContext('2d')!;
  122. tempCtx.drawImage(this.canvasRef.nativeElement, 0, 0);
  123. tempCtx.beginPath();
  124. tempCtx.rect(this.lastX, this.lastY, pos.x - this.lastX, pos.y - this.lastY);
  125. tempCtx.strokeStyle = this.currentColor;
  126. tempCtx.lineWidth = this.currentWidth;
  127. tempCtx.stroke();
  128. this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height);
  129. this.ctx.drawImage(tempCanvas, 0, 0);
  130. }
  131. private drawCircle(pos: { x: number, y: number }) {
  132. const tempCanvas = document.createElement('canvas');
  133. tempCanvas.width = this.canvasRef.nativeElement.width;
  134. tempCanvas.height = this.canvasRef.nativeElement.height;
  135. const tempCtx = tempCanvas.getContext('2d')!;
  136. const radius = Math.sqrt(
  137. Math.pow(pos.x - this.lastX, 2) + Math.pow(pos.y - this.lastY, 2)
  138. );
  139. tempCtx.drawImage(this.canvasRef.nativeElement, 0, 0);
  140. tempCtx.beginPath();
  141. tempCtx.arc(this.lastX, this.lastY, radius, 0, Math.PI * 2);
  142. tempCtx.strokeStyle = this.currentColor;
  143. tempCtx.lineWidth = this.currentWidth;
  144. tempCtx.stroke();
  145. this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height);
  146. this.ctx.drawImage(tempCanvas, 0, 0);
  147. }
  148. stopDrawing() {
  149. if (this.isDrawing) {
  150. this.isDrawing = false;
  151. this.saveToHistory();
  152. }
  153. }
  154. private getPosition(event: TouchEvent | MouseEvent) {
  155. const canvas = this.canvasRef.nativeElement;
  156. const rect = canvas.getBoundingClientRect();
  157. let x, y;
  158. if (event instanceof TouchEvent) {
  159. x = event.touches[0].clientX - rect.left;
  160. y = event.touches[0].clientY - rect.top;
  161. } else {
  162. x = event.clientX - rect.left;
  163. y = event.clientY - rect.top;
  164. }
  165. return { x, y };
  166. }
  167. selectTool(toolId: string) {
  168. this.selectedTool = toolId;
  169. if (toolId === 'clear') {
  170. this.clearCanvas();
  171. }
  172. }
  173. selectColor(color: string) {
  174. this.currentColor = color;
  175. this.selectedTool = 'brush';
  176. }
  177. clearCanvas() {
  178. this.ctx.fillStyle = '#ffffff';
  179. this.ctx.fillRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height);
  180. }
  181. async saveDrawing() {
  182. const canvas = this.canvasRef.nativeElement;
  183. try {
  184. const dataUrl = canvas.toDataURL('image/png');
  185. const response = await fetch(dataUrl);
  186. const blob = await response.blob();
  187. const url = window.URL.createObjectURL(blob);
  188. const a = document.createElement('a');
  189. a.href = url;
  190. a.download = `绘画_${new Date().getTime()}.png`;
  191. document.body.appendChild(a);
  192. a.click();
  193. window.URL.revokeObjectURL(url);
  194. document.body.removeChild(a);
  195. this.showToast('保存成功');
  196. } catch (error) {
  197. this.showToast('保存失败,请重试');
  198. }
  199. }
  200. private async showToast(message: string) {
  201. const toast = document.createElement('ion-toast');
  202. toast.message = message;
  203. toast.duration = 2000;
  204. toast.position = 'top';
  205. document.body.appendChild(toast);
  206. await toast.present();
  207. }
  208. undo() {
  209. if (this.historyIndex > 0) {
  210. this.historyIndex--;
  211. const imageData = this.history[this.historyIndex];
  212. this.ctx.putImageData(imageData, 0, 0);
  213. }
  214. }
  215. redo() {
  216. if (this.historyIndex < this.history.length - 1) {
  217. this.historyIndex++;
  218. const imageData = this.history[this.historyIndex];
  219. this.ctx.putImageData(imageData, 0, 0);
  220. }
  221. }
  222. saveToHistory() {
  223. this.historyIndex++;
  224. this.history = this.history.slice(0, this.historyIndex);
  225. this.history.push(this.ctx.getImageData(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height));
  226. }
  227. toggleGrid() {
  228. this.showGrid = !this.showGrid;
  229. }
  230. toggleOpacity() {
  231. this.opacity = this.opacity === 1 ? 0.5 : 1;
  232. this.ctx.globalAlpha = this.opacity;
  233. }
  234. toggleSmooth() {
  235. this.smoothing = !this.smoothing;
  236. this.ctx.imageSmoothingEnabled = this.smoothing;
  237. }
  238. toggleLayers() {
  239. this.showLayers = !this.showLayers;
  240. }
  241. addLayer() {
  242. const newId = `layer${this.layers.length + 1}`;
  243. this.layers.push({
  244. id: newId,
  245. name: `图层 ${this.layers.length + 1}`,
  246. visible: true
  247. });
  248. this.currentLayer = newId;
  249. }
  250. deleteLayer(layerId: string) {
  251. if (this.layers.length > 1) {
  252. this.layers = this.layers.filter(layer => layer.id !== layerId);
  253. if (this.currentLayer === layerId) {
  254. this.currentLayer = this.layers[0].id;
  255. }
  256. }
  257. }
  258. }