import { Component, OnInit, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; import { IonicModule } from '@ionic/angular'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; @Component({ selector: 'app-drawing', templateUrl: './drawing.component.html', styleUrls: ['./drawing.component.scss'], standalone: true, imports: [IonicModule, CommonModule, FormsModule], }) export class DrawingComponent implements OnInit, AfterViewInit { @ViewChild('canvas') canvasRef!: ElementRef; private ctx!: CanvasRenderingContext2D; private isDrawing = false; private lastX = 0; private lastY = 0; currentColor = '#000000'; currentWidth = 5; customColor = '#000000'; showGrid = false; opacity = 1; smoothing = true; showLayers = false; currentLayer = 'layer1'; layers = [ { id: 'layer1', name: '图层 1', visible: true }, { id: 'layer2', name: '图层 2', visible: true } ]; history: ImageData[] = []; historyIndex = -1; tools = [ { id: 'brush', icon: 'brush', name: '画笔' }, { id: 'eraser', icon: 'trash', name: '橡皮擦' }, { id: 'line', icon: 'remove', name: '直线' }, { id: 'rect', icon: 'square-outline', name: '矩形' }, { id: 'circle', icon: 'ellipse-outline', name: '圆形' }, { id: 'clear', icon: 'refresh', name: '清空' } ]; colors = [ '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#808080', '#800000' ]; selectedTool = 'brush'; constructor() { } ngOnInit() { } ngAfterViewInit() { const canvas = this.canvasRef.nativeElement; this.ctx = canvas.getContext('2d')!; this.resizeCanvas(); this.initializeCanvas(); window.addEventListener('resize', () => { this.resizeCanvas(); }); } private resizeCanvas() { const canvas = this.canvasRef.nativeElement; canvas.width = window.innerWidth; canvas.height = window.innerHeight - 200; // 减去顶部工具栏的高度 } private initializeCanvas() { this.ctx.fillStyle = '#ffffff'; this.ctx.fillRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); } startDrawing(event: TouchEvent | MouseEvent) { this.isDrawing = true; const pos = this.getPosition(event); this.lastX = pos.x; this.lastY = pos.y; } draw(event: TouchEvent | MouseEvent) { if (!this.isDrawing) return; event.preventDefault(); const pos = this.getPosition(event); switch (this.selectedTool) { case 'brush': case 'eraser': this.drawFreehand(pos); break; case 'line': this.drawLine(pos); break; case 'rect': this.drawRect(pos); break; case 'circle': this.drawCircle(pos); break; } } private drawFreehand(pos: { x: number, y: number }) { this.ctx.beginPath(); this.ctx.moveTo(this.lastX, this.lastY); this.ctx.lineTo(pos.x, pos.y); this.ctx.strokeStyle = this.selectedTool === 'eraser' ? '#ffffff' : this.currentColor; this.ctx.lineWidth = this.currentWidth; this.ctx.lineCap = 'round'; this.ctx.stroke(); this.lastX = pos.x; this.lastY = pos.y; } private drawLine(pos: { x: number, y: number }) { const tempCanvas = document.createElement('canvas'); tempCanvas.width = this.canvasRef.nativeElement.width; tempCanvas.height = this.canvasRef.nativeElement.height; const tempCtx = tempCanvas.getContext('2d')!; tempCtx.drawImage(this.canvasRef.nativeElement, 0, 0); tempCtx.beginPath(); tempCtx.moveTo(this.lastX, this.lastY); tempCtx.lineTo(pos.x, pos.y); tempCtx.strokeStyle = this.currentColor; tempCtx.lineWidth = this.currentWidth; tempCtx.stroke(); this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); this.ctx.drawImage(tempCanvas, 0, 0); } private drawRect(pos: { x: number, y: number }) { const tempCanvas = document.createElement('canvas'); tempCanvas.width = this.canvasRef.nativeElement.width; tempCanvas.height = this.canvasRef.nativeElement.height; const tempCtx = tempCanvas.getContext('2d')!; tempCtx.drawImage(this.canvasRef.nativeElement, 0, 0); tempCtx.beginPath(); tempCtx.rect(this.lastX, this.lastY, pos.x - this.lastX, pos.y - this.lastY); tempCtx.strokeStyle = this.currentColor; tempCtx.lineWidth = this.currentWidth; tempCtx.stroke(); this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); this.ctx.drawImage(tempCanvas, 0, 0); } private drawCircle(pos: { x: number, y: number }) { const tempCanvas = document.createElement('canvas'); tempCanvas.width = this.canvasRef.nativeElement.width; tempCanvas.height = this.canvasRef.nativeElement.height; const tempCtx = tempCanvas.getContext('2d')!; const radius = Math.sqrt( Math.pow(pos.x - this.lastX, 2) + Math.pow(pos.y - this.lastY, 2) ); tempCtx.drawImage(this.canvasRef.nativeElement, 0, 0); tempCtx.beginPath(); tempCtx.arc(this.lastX, this.lastY, radius, 0, Math.PI * 2); tempCtx.strokeStyle = this.currentColor; tempCtx.lineWidth = this.currentWidth; tempCtx.stroke(); this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); this.ctx.drawImage(tempCanvas, 0, 0); } stopDrawing() { if (this.isDrawing) { this.isDrawing = false; this.saveToHistory(); } } private getPosition(event: TouchEvent | MouseEvent) { const canvas = this.canvasRef.nativeElement; const rect = canvas.getBoundingClientRect(); let x, y; if (event instanceof TouchEvent) { x = event.touches[0].clientX - rect.left; y = event.touches[0].clientY - rect.top; } else { x = event.clientX - rect.left; y = event.clientY - rect.top; } return { x, y }; } selectTool(toolId: string) { this.selectedTool = toolId; if (toolId === 'clear') { this.clearCanvas(); } } selectColor(color: string) { this.currentColor = color; this.selectedTool = 'brush'; } clearCanvas() { this.ctx.fillStyle = '#ffffff'; this.ctx.fillRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); } async saveDrawing() { const canvas = this.canvasRef.nativeElement; try { const dataUrl = canvas.toDataURL('image/png'); const response = await fetch(dataUrl); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `绘画_${new Date().getTime()}.png`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); this.showToast('保存成功'); } catch (error) { this.showToast('保存失败,请重试'); } } private async showToast(message: string) { const toast = document.createElement('ion-toast'); toast.message = message; toast.duration = 2000; toast.position = 'top'; document.body.appendChild(toast); await toast.present(); } undo() { if (this.historyIndex > 0) { this.historyIndex--; const imageData = this.history[this.historyIndex]; this.ctx.putImageData(imageData, 0, 0); } } redo() { if (this.historyIndex < this.history.length - 1) { this.historyIndex++; const imageData = this.history[this.historyIndex]; this.ctx.putImageData(imageData, 0, 0); } } saveToHistory() { this.historyIndex++; this.history = this.history.slice(0, this.historyIndex); this.history.push(this.ctx.getImageData(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height)); } toggleGrid() { this.showGrid = !this.showGrid; } toggleOpacity() { this.opacity = this.opacity === 1 ? 0.5 : 1; this.ctx.globalAlpha = this.opacity; } toggleSmooth() { this.smoothing = !this.smoothing; this.ctx.imageSmoothingEnabled = this.smoothing; } toggleLayers() { this.showLayers = !this.showLayers; } addLayer() { const newId = `layer${this.layers.length + 1}`; this.layers.push({ id: newId, name: `图层 ${this.layers.length + 1}`, visible: true }); this.currentLayer = newId; } deleteLayer(layerId: string) { if (this.layers.length > 1) { this.layers = this.layers.filter(layer => layer.id !== layerId); if (this.currentLayer === layerId) { this.currentLayer = this.layers[0].id; } } } }