319 lines
9.4 KiB
TypeScript
319 lines
9.4 KiB
TypeScript
// 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 { FormsModule } from '@angular/forms';
|
|
import { Modal } from 'flowbite';
|
|
|
|
@Component({
|
|
selector: 'app-upload-image-modal',
|
|
templateUrl: './upload-image-modal.component.html',
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule]
|
|
})
|
|
export class UploadImageModalComponent implements AfterViewInit, OnDestroy {
|
|
@Input() deckName: string = '';
|
|
@Output() imageUploaded = new EventEmitter<void>();
|
|
|
|
@ViewChild('uploadImageModal') modalElement!: ElementRef;
|
|
@ViewChild('canvas') canvasElement!: ElementRef<HTMLCanvasElement>;
|
|
|
|
imageFile: File | null = null;
|
|
imageText: string = '';
|
|
modal!: any; // Typ kann je nach Flowbite-Version angepasst werden
|
|
canvas!: fabric.Canvas;
|
|
|
|
// Fabric.js Related Variables
|
|
originalImageSrc: string | ArrayBuffer | undefined | null = null;
|
|
detectedText: string = '';
|
|
processingStatus: string = '';
|
|
loading: boolean = false;
|
|
|
|
// Maximal erlaubte Größe
|
|
maxCanvasWidth: number = 0;
|
|
maxCanvasHeight: number = 0;
|
|
|
|
boxes: { x1: number; x2: number; y1: number; y2: number }[] = [];
|
|
|
|
originalImageWidth: number = 0;
|
|
originalImageHeight: number = 0;
|
|
|
|
// Referenz zum Keydown-Eventhandler
|
|
private keyDownHandler!: (e: KeyboardEvent) => void;
|
|
|
|
constructor(private deckService: DeckService, private http: HttpClient) { }
|
|
|
|
ngAfterViewInit(): void {
|
|
// Initialisiere die Flowbite Modal
|
|
this.modal = new Modal(this.modalElement.nativeElement);
|
|
|
|
// Initialisiere Fabric.js Canvas
|
|
this.canvas = new fabric.Canvas(this.canvasElement.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.imageText = '';
|
|
this.originalImageSrc = null;
|
|
this.detectedText = '';
|
|
this.processingStatus = '';
|
|
this.loading = false;
|
|
this.boxes = [];
|
|
this.originalImageWidth = 0;
|
|
this.originalImageHeight = 0;
|
|
this.canvas.clear();
|
|
}
|
|
|
|
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<void> {
|
|
const file: File = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
// Status zurücksetzen
|
|
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 {
|
|
// Anfrage an den Backend-Service senden
|
|
const response = await this.http.post<any>('/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
|
|
await this.processImage(response.results);
|
|
} catch (error) {
|
|
console.error('Fehler beim OCR-Service:', error);
|
|
this.processingStatus = 'Fehler beim OCR-Service';
|
|
this.loading = false;
|
|
}
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
|
|
private loadFabricImage(url: string): Promise<fabric.Image> {
|
|
return new Promise((resolve, reject) => {
|
|
fabric.Image.fromURL(
|
|
url,
|
|
(img) => {
|
|
resolve(img);
|
|
},
|
|
{
|
|
crossOrigin: 'anonymous',
|
|
originX: 'left',
|
|
originY: 'top',
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async processImage(ocrResults: any[]): Promise<void> {
|
|
// Canvas zurücksetzen
|
|
this.canvas.clear();
|
|
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.5)',
|
|
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;
|
|
|
|
// Eingabefelder und Button ausblenden
|
|
this.hideFormElements();
|
|
} 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();
|
|
}
|
|
|
|
hideFormElements(): void {
|
|
// Implementiere die Logik, um die Eingabefelder und den Button auszublenden
|
|
// Dies kann durch eine zusätzliche Variable gesteuert werden
|
|
this.formVisible = false;
|
|
this.canvasVisible = true;
|
|
}
|
|
|
|
showFormElements(): void {
|
|
this.formVisible = true;
|
|
this.canvasVisible = false;
|
|
}
|
|
|
|
// Neue Variable zur Steuerung der Sichtbarkeit
|
|
formVisible: boolean = true;
|
|
canvasVisible: boolean = true;
|
|
}
|
|
|