// src/app/upload-image-modal.component.ts import { Component, Input, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef, OnDestroy } from '@angular/core'; import { DeckService } from '../deck.service'; import { CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { fabric } from 'fabric'; import { Modal } from 'flowbite'; @Component({ selector: 'app-upload-image-modal', templateUrl: './upload-image-modal.component.html', standalone: true, imports: [CommonModule] }) export class UploadImageModalComponent implements AfterViewInit, OnDestroy { @Input() deckName: string = ''; @Output() imageUploaded = new EventEmitter(); @ViewChild('uploadImageModal') modalElement!: ElementRef; @ViewChild('canvas') canvasElement!: ElementRef; imageFile: File | null = null; processingStatus: string = ''; loading: boolean = false; // Fabric.js Related Variables originalImageSrc: string | ArrayBuffer | undefined | null = null; detectedText: string = ''; boxes: { x1: number; x2: number; y1: number; y2: number }[] = []; canvas!: fabric.Canvas; // Maximal erlaubte Größe maxCanvasWidth: number = 0; maxCanvasHeight: number = 0; // Referenz zum Keydown-Eventhandler private keyDownHandler!: (e: KeyboardEvent) => void; // State Management formVisible: boolean = true; canvasVisible: boolean = false; modal:any; originalImageWidth:number|undefined; originalImageHeight:number|undefined; imagename:string|undefined|null; constructor(private deckService: DeckService, private http: HttpClient) { } ngAfterViewInit(): void { // Initialisiere die Flowbite Modal this.modal = new Modal(this.modalElement.nativeElement); // Berechne die maximal erlaubten Abmessungen basierend auf dem Viewport this.maxCanvasWidth = window.innerWidth * 0.6; // Passe nach Bedarf an this.maxCanvasHeight = window.innerHeight * 0.6; // Passe nach Bedarf an // Keydown-Eventlistener hinzufügen this.keyDownHandler = this.onKeyDown.bind(this); document.addEventListener('keydown', this.keyDownHandler); } ngOnDestroy(): void { // Keydown-Eventlistener entfernen document.removeEventListener('keydown', this.keyDownHandler); // Fabric.js Canvas zerstören if (this.canvas) { this.canvas.dispose(); } } open(): void { this.resetState(); this.modal.show(); } closeModal(): void { this.modal.hide(); } resetState(): void { this.imageFile = null; this.processingStatus = ''; this.detectedText = ''; this.boxes = []; this.originalImageSrc = null; this.canvasVisible = false; this.formVisible = true; // Clear Fabric canvas if it exists if (this.canvas) { this.canvas.clear(); this.canvas.dispose(); this.canvas = undefined as any; } } onKeyDown(e: KeyboardEvent): void { if (e.key === 'Delete' || e.key === 'Del') { const activeObject = this.canvas.getActiveObject(); if (activeObject) { this.canvas.remove(activeObject); this.canvas.requestRenderAll(); this.updateBoxCoordinates(); } } } async onFileChange(event: any): Promise { const file: File = event.target.files[0]; if (!file) return; this.imageFile = file; this.processingStatus = 'Verarbeitung läuft...'; this.detectedText = ''; this.loading = true; // Bild als Base64 laden const reader = new FileReader(); reader.onload = async (e) => { this.originalImageSrc = e.target?.result; // Bild als Base64-String ohne Präfix (data:image/...) const imageBase64 = (this.originalImageSrc as string).split(',')[1]; try { // Eingabefelder ausblenden und Canvas anzeigen this.formVisible = false; this.canvasVisible = true; // Anfrage an den Backend-Service senden const response = await this.http.post('/api/ocr', { image: imageBase64 }).toPromise(); if (!response || !response.results) { this.processingStatus = 'Ungültige Antwort vom OCR-Service'; this.loading = false; return; } // Bildverarbeitung im Frontend durchführen this.imagename = (response.results && response.results.length>0)?response.results[0].name:null; await this.processImage(response.results); } catch (error) { console.error('Fehler beim OCR-Service:', error); this.processingStatus = 'Fehler beim OCR-Service'; this.loading = false; // Eingabefelder ausblenden und Canvas anzeigen this.formVisible = true; this.canvasVisible = false; } }; reader.readAsDataURL(file); } private loadFabricImage(url: string): Promise { return new Promise((resolve, reject) => { fabric.Image.fromURL( url, (img) => { resolve(img); }, { crossOrigin: 'anonymous', originX: 'left', originY: 'top', } ); }); } async processImage(ocrResults: any[]): Promise { // Canvas zurücksetzen if (this.canvas) { this.canvas.clear(); this.canvas.dispose(); } this.canvas = new fabric.Canvas(this.canvasElement.nativeElement); this.boxes = []; // Hintergrundbild setzen try { const backgroundImage = await this.loadFabricImage(this.originalImageSrc as string); // Speichere die Originalbildgröße this.originalImageWidth = backgroundImage.width!; this.originalImageHeight = backgroundImage.height!; // Berechne Skalierungsfaktor basierend auf maximal erlaubter Größe const scaleX = this.maxCanvasWidth / backgroundImage.width!; const scaleY = this.maxCanvasHeight / backgroundImage.height!; const scaleFactor = Math.min(scaleX, scaleY, 1); // Vermeide Vergrößerung // Neue Größe des Canvas const canvasWidth = backgroundImage.width! * scaleFactor; const canvasHeight = backgroundImage.height! * scaleFactor; // Canvas-Größe anpassen this.canvas.setWidth(canvasWidth); this.canvas.setHeight(canvasHeight); // Hintergrundbild skalieren backgroundImage.set({ scaleX: scaleFactor, scaleY: scaleFactor, }); // Hintergrundbild setzen this.canvas.setBackgroundImage(backgroundImage, this.canvas.renderAll.bind(this.canvas)); // Boxen hinzufügen ocrResults.forEach(result => { const box = result.box; // Grenzen berechnen const xs = box.map((point: number[]) => point[0]); const ys = box.map((point: number[]) => point[1]); const xMin = Math.min(...xs); const xMax = Math.max(...xs); const yMin = Math.min(...ys); const yMax = Math.max(...ys); // Skalierung anwenden const left = xMin * scaleFactor; const top = yMin * scaleFactor; const width = (xMax - xMin) * scaleFactor; const height = (yMax - yMin) * scaleFactor; // Rechteck erstellen const rect = new fabric.Rect({ left: left, top: top, width: width, height: height, fill: 'rgba(0, 0, 0, 0.3)', selectable: true, hasControls: true, hasBorders: true, objectCaching: false, }); // Event-Listener hinzufügen rect.on('modified', () => { this.updateBoxCoordinates(); }); rect.on('moved', () => { this.updateBoxCoordinates(); }); rect.on('scaled', () => { this.updateBoxCoordinates(); }); rect.on('rotated', () => { this.updateBoxCoordinates(); }); rect.on('removed', () => { this.updateBoxCoordinates(); }); this.canvas.add(rect); }); // Initiale Box-Koordinaten aktualisieren this.updateBoxCoordinates(); // Erkannten Text anzeigen this.detectedText = ocrResults.map(result => result.text).join('\n'); this.processingStatus = 'Verarbeitung abgeschlossen'; this.loading = false; } catch (error) { console.error('Fehler beim Setzen des Hintergrundbildes:', error); this.processingStatus = 'Fehler bei der Bildverarbeitung'; this.loading = false; } } updateBoxCoordinates(): void { // Leere die aktuelle Box-Liste this.boxes = []; // Skalierungsfaktor ermitteln (sollte der gleiche sein wie zuvor) let scaleFactor = 1; const bgImage = this.canvas.backgroundImage; if (bgImage && bgImage instanceof fabric.Image) { scaleFactor = bgImage.get('scaleX') || 1; } // Alle Rechtecke durchgehen this.canvas.getObjects('rect').forEach((rect: fabric.Rect) => { // Aktuelle Position und Größe des Rechtecks const left = rect.left!; const top = rect.top!; const width = rect.width! * rect.scaleX!; const height = rect.height! * rect.scaleY!; // Umrechnung auf Originalbildgröße const x1 = left / scaleFactor; const y1 = top / scaleFactor; const x2 = (left + width) / scaleFactor; const y2 = (top + height) / scaleFactor; // Werte runden this.boxes.push({ x1: Math.round(x1), x2: Math.round(x2), y1: Math.round(y1), y2: Math.round(y2) }); }); // Trigger Angular Change Detection this.canvas.requestRenderAll(); } save(): void { // Hier kannst du die Logik zum Speichern der Bilddaten implementieren // Zum Beispiel: const data = { deckname: this.deckName, bildname: this.imageFile?.name, bildid: this.imagename, boxes: this.boxes, }; this.deckService.saveImageData(data).subscribe({ next: () => { this.imageUploaded.emit(); this.closeModal(); }, error: (err) => { console.error('Fehler beim Speichern des Bildes:', err); alert('Fehler beim Speichern des Bildes.'); } }); // Temporäres Beispiel: this.imageUploaded.emit(); this.closeModal(); } }