Zunächst muss ich sagen: Ich habe keine Ahnung vom Programmieren – ausser ein wenig HTML. Doch als ich las: „Programmieren mit KI ist ganz einfach“, liess mir das keine Ruhe. Ich wollte es selbst ausprobieren.
Tatsächlich habe ich es geschafft, ein Programm zu erstellen: einen Bildbetrachter mit Bearbeitungsfunktionen in Python. Allerdings war das alles andere als einfach – und ich habe dabei eine Menge gelernt.
Ich habe nicht nur Python ausprobiert, sondern auch C, C++ und Rust. Letztendlich blieb ich bei Python, da es für mich am zugänglichsten war und die KI dafür die meisten Informationen bereitstellen konnte. Aber selbst mit KI-Unterstützung war es eine Herausforderung. Ein Programm ist nie wirklich „fertig“ – es gibt immer etwas zu verbessern, zu ergänzen oder zu verschönern.
Ich habe viel mehr Zeit investiert, als ich ursprünglich dachte. Programmieren mit KI ist also doch nicht so einfach, besonders wenn das Projekt umfangreicher wird. Ständiges Testen ist unerlässlich: testen, KI fragen, testen, erneut fragen – und oft die Fragen anders formulieren, um bessere Antworten zu erhalten.
Da ich kein kostenpflichtiges Abo abschliessen wollte, habe ich ausschliesslich kostenlose Versionen von ChatGPT, Claude, DeepSeek und You.com genutzt. Um genügend Kapazität zu haben, erstellte ich mehrere Accounts und wechselte ständig – ein mühsamer Prozess. Auch das Optimieren der Fragen, das Finden neuer Ansätze und das mehrfache Nachfragen war zeitaufwendig und oft frustrierend.
Viele Probleme blieben ungelöst, da die KI entweder keine Lösung fand oder die Frage zu komplex war. Häufig „zerstörte“ die KI mit ihren vermeintlichen Verbesserungen bereits funktionierende Teile des Codes.
Ich habe gelernt, dass es am besten ist, sich pro Problem nur auf eine Funktion zu konzentrieren, bis es gelöst ist. Zudem hilft es enorm, den Code in mehrere Dateien aufzuteilen – das macht ihn übersichtlicher und erleichtert es der KI, gezielt zu helfen, ohne bestehende Funktionen zu beeinträchtigen.
Ich habe viel gelernt, und das ist grossartig. Aber es war eine echte Knochenarbeit, die viel Durchhaltevermögen erforderte. Die KI kann viele Lösungen liefern – doch man muss wissen, wie man sie dazu bringt. Für komplexe Programme ist KI-gestütztes Programmieren in der Praxis nicht machbar.
Hier das Hauptscript viewer.py für meine Anwendung: ImageViewer.
Das ganze Programm besteht noch aus den Dateien: gallery.py, crop.py, resize.py, rotation.py, config.py und config.txt.
from crop import CroppableImageLabel, ImageCropperWidget
from resize import resize_image # Add this at the top of viewer.py
from resize import resize_current_image as resize_dialog
import sys
import os
import shutil # Für das Kopieren von Dateien
from PyQt6.QtWidgets import (QMainWindow, QApplication, QVBoxLayout, QWidget,
QFileDialog, QMessageBox, QToolBar, QLabel, QCheckBox,
QDialog, QFormLayout, QLineEdit, QDialogButtonBox, QPushButton, QHBoxLayout, QRubberBand, QSizePolicy, QStackedWidget, QInputDialog, QLineEdit, QMenu)
from PyQt6.QtGui import QAction, QIcon, QPixmap, QPainter, QPen, QColor, QCursor, QPixmapCache, QKeyEvent, QFont, QFontMetrics
from PyQt6.QtCore import Qt, QRect, QPoint, pyqtSignal, QSize, QTimer
from config import save_config, load_config
from gallery import ImageGallery
from rotation import rotate_image, show_rotation_dialog
class ImageViewerApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle(„Image Viewer“)
self.normal_size = QSize(1280, 800)
self.resize(1280, 800)
self.normal_position = self.pos()
self.image_paths = []
self.current_index = -1
# Zentrales Widget mit QStackedWidget für ImageViewer & Galerie
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Falls bereits ein Layout existiert, entfernen
if central_widget.layout() is not None:
old_layout = central_widget.layout()
QWidget().setLayout(old_layout) # Altes Layout „freigeben“
# Neues Layout setzen
self.layout = QVBoxLayout()
central_widget.setLayout(self.layout)
# StackedWidget für Viewer & Galerie
self.stacked_widget = QStackedWidget()
self.layout.addWidget(self.stacked_widget)
# Bildanzeige als QLabel
self.image_viewer = QLabel(self)
self.image_viewer.setAlignment(Qt.AlignmentFlag.AlignCenter) # Zentriere das Bild
self.image_viewer.setText(„Kein Bild geladen“) # Platzhaltertext, wenn kein Bild geladen ist
self.stacked_widget.addWidget(self.image_viewer)
# **Bildinformationen-Label erstellen**
self.image_info_label = QLabel(„Keine Bildinformationen“)
# Erst jetzt das Verzeichnis laden!
self.select_initial_directory()
self._pixmap = QPixmap() # Speichert das aktuelle Pixmap
# Galerie
self.gallery_widget = ImageGallery(self.image_directory)
self.gallery_widget.image_clicked.connect(self.handle_gallery_image_click)
self.stacked_widget.addWidget(self.gallery_widget) # Index 1
# Menü und Toolbar erstellen
self.create_menu()
self.create_toolbar()
# **WICHTIG: Sicherstellen, dass der ImageViewer beim Start angezeigt wird**
self.stacked_widget.setCurrentIndex(0)
if self.image_paths: # Nur aufrufen, wenn die Liste nicht leer ist!
self.current_index = 0
self.load_image(self.image_paths[0])
self.update_image_info(self.image_paths[0])
else:
self.image_viewer.setText(„Kein Bild gefunden“)
# Crop-Funktionalität einrichten
self.setup_crop_functionality()
def create_menu(self):
„““Erstellt das Menü.“““
self.menubar = self.menuBar()
self.file_menu = self.menubar.addMenu(„&Datei“)
# Datei öffnen
self.open_action = QAction(QIcon.fromTheme(„document-open“), „Datei öffnen“, self)
self.open_action.triggered.connect(self.open_file)
self.file_menu.addAction(self.open_action)
# Verzeichnis wählen
self.select_dir_action = QAction(QIcon.fromTheme(„folder-open“), „Bildverzeichnis wählen“, self)
self.select_dir_action.triggered.connect(self.change_directory)
self.file_menu.addAction(self.select_dir_action)
# Aktionen für die Bildansicht (werden dynamisch hinzugefügt/entfernt)
self.save_action = QAction(QIcon.fromTheme(„document-save“), „Speichern“, self)
self.save_action.triggered.connect(self.save_image)
self.file_menu.addAction(self.save_action)
self.save_as_action = QAction(QIcon.fromTheme(„document-save-as“), „Speichern unter“, self)
self.save_as_action.triggered.connect(self.save_image_as)
self.file_menu.addAction(self.save_as_action)
# Umbenennen
self.rename_action = QAction(QIcon.fromTheme(„edit-rename“), „Umbenennen“, self)
self.rename_action.triggered.connect(self.rename_image)
self.file_menu.addAction(self.rename_action)
# Sortiermenü als Untermenü zu Datei hinzufügen
self.sort_menu = self.file_menu.addMenu(QIcon.fromTheme(„view-sort-ascending“), „Bilder sortieren“)
# Name sortieren
sort_name_asc = QAction(„Name (aufsteigend)“, self)
sort_name_asc.triggered.connect(lambda: self.sort_images(„name“, „asc“))
self.sort_menu.addAction(sort_name_asc)
sort_name_desc = QAction(„Name (absteigend)“, self)
sort_name_desc.triggered.connect(lambda: self.sort_images(„name“, „desc“))
self.sort_menu.addAction(sort_name_desc)
# Datum sortieren
sort_date_asc = QAction(„Datum (aufsteigend)“, self)
sort_date_asc.triggered.connect(lambda: self.sort_images(„date“, „asc“))
self.sort_menu.addAction(sort_date_asc)
sort_date_desc = QAction(„Datum (absteigend)“, self)
sort_date_desc.triggered.connect(lambda: self.sort_images(„date“, „desc“))
self.sort_menu.addAction(sort_date_desc)
# Größe sortieren
sort_size_asc = QAction(„Größe (aufsteigend)“, self)
sort_size_asc.triggered.connect(lambda: self.sort_images(„size“, „asc“))
self.sort_menu.addAction(sort_size_asc)
sort_size_desc = QAction(„Größe (absteigend)“, self)
sort_size_desc.triggered.connect(lambda: self.sort_images(„size“, „desc“))
self.sort_menu.addAction(sort_size_desc)
# Crop-Aktion hinzufügen
self.crop_action = QAction(QIcon.fromTheme(„transform-crop“), „Bild zuschneiden“, self)
self.crop_action.triggered.connect(self.start_crop_mode)
self.file_menu.addAction(self.crop_action)
# Add this in the create_menu method, in the file_menu section
self.resize_action = QAction(QIcon.fromTheme(„transform-scale“), „Bildgröße ändern“, self)
self.resize_action.triggered.connect(self.resize_current_image)
self.file_menu.addAction(self.resize_action)
self.rotate_action = QAction(QIcon.fromTheme(„object-rotate-right“), „Bild drehen“, self)
self.rotate_action.triggered.connect(self.rotate_current_image)
self.file_menu.addAction(self.rotate_action)
self.delete_action = QAction(QIcon.fromTheme(„edit-delete“), „Datei löschen“, self)
self.delete_action.triggered.connect(self.delete_image)
self.file_menu.addAction(self.delete_action)
# Beenden
self.exit_action = QAction(QIcon.fromTheme(„application-exit“), „Beenden“, self)
self.exit_action.triggered.connect(self.close)
self.file_menu.addAction(self.exit_action)
def create_toolbar(self):
„““Erstellt die Toolbar.“““
self.toolbar = QToolBar(„Hauptwerkzeuge“)
self.addToolBar(self.toolbar)
# Gallery Button hinzufügen
self.gallery_btn = QAction(QIcon.fromTheme(„view-list-icons“), „Galerie öffnen“, self)
self.gallery_btn.triggered.connect(self.show_gallery)
self.toolbar.addAction(self.gallery_btn)
# Bildspeichern-Buttons
self.save_btn = QAction(QIcon.fromTheme(„document-save“), „Speichern“, self)
self.save_btn.triggered.connect(self.save_image)
self.toolbar.addAction(self.save_btn)
self.save_as_btn = QAction(QIcon.fromTheme(„document-save-as“), „Speichern unter“, self)
self.save_as_btn.triggered.connect(self.save_image_as)
self.toolbar.addAction(self.save_as_btn)
self.rename_btn = QAction(QIcon.fromTheme(„edit-rename“), „Umbenennen“, self)
self.rename_btn.triggered.connect(self.rename_image)
self.toolbar.addAction(self.rename_btn)
# Sortiermenü als Drop-Down-Button
self.sort_btn = QAction(QIcon.fromTheme(„view-sort-ascending“), „Sortieren“, self)
self.sort_menu = QMenu(self)
# Name sortieren
sort_name_asc = QAction(„↑ Name“, self)
sort_name_asc.triggered.connect(lambda: self.sort_images(„name“, „asc“))
self.sort_menu.addAction(sort_name_asc)
sort_name_desc = QAction(„↓ Name“, self)
sort_name_desc.triggered.connect(lambda: self.sort_images(„name“, „desc“))
self.sort_menu.addAction(sort_name_desc)
# Datum sortieren
sort_date_asc = QAction(„↑ Datum“, self)
sort_date_asc.triggered.connect(lambda: self.sort_images(„date“, „asc“))
self.sort_menu.addAction(sort_date_asc)
sort_date_desc = QAction(„↓ Datum“, self)
sort_date_desc.triggered.connect(lambda: self.sort_images(„date“, „desc“))
self.sort_menu.addAction(sort_date_desc)
# Größe sortieren
sort_size_asc = QAction(„↑ Größe“, self)
sort_size_asc.triggered.connect(lambda: self.sort_images(„size“, „asc“))
self.sort_menu.addAction(sort_size_asc)
sort_size_desc = QAction(„↓ Größe“, self)
sort_size_desc.triggered.connect(lambda: self.sort_images(„size“, „desc“))
self.sort_menu.addAction(sort_size_desc)
self.sort_btn.setMenu(self.sort_menu)
self.toolbar.addAction(self.sort_btn)
# Crop Button mit eigener, separater Aktion
self.crop_btn = QAction(QIcon.fromTheme(„transform-crop“), „Zuschneiden“, self)
self.crop_btn.triggered.connect(self.start_crop_mode) # Verbindet mit start_crop_mode
self.toolbar.addAction(self.crop_btn)
# Add this in the create_toolbar method, near your other toolbar buttons
self.resize_btn = QAction(QIcon.fromTheme(„transform-scale“), „Größe ändern“, self)
self.resize_btn.triggered.connect(self.resize_current_image)
self.toolbar.addAction(self.resize_btn)
self.rotate_btn = QAction(QIcon.fromTheme(„object-rotate-right“), „Drehen“, self)
self.rotate_btn.triggered.connect(self.rotate_current_image)
self.toolbar.addAction(self.rotate_btn)
self.delete_btn = QAction(QIcon.fromTheme(„edit-delete“), „Löschen“, self)
self.delete_btn.triggered.connect(self.delete_image)
self.toolbar.addAction(self.delete_btn)
# Vollbild
self.fullscreen_btn = QAction(QIcon.fromTheme(„view-fullscreen“), „Vollbild“, self)
self.fullscreen_btn.triggered.connect(self.toggle_fullscreen)
self.toolbar.addAction(self.fullscreen_btn)
# Vorheriges Bild
self.prev_btn = QAction(QIcon.fromTheme(„go-previous“), „Vorheriges Bild“, self)
self.prev_btn.triggered.connect(lambda: self.navigate_image(-1))
self.toolbar.addAction(self.prev_btn)
# Nächstes Bild
self.next_btn = QAction(QIcon.fromTheme(„go-next“), „Nächstes Bild“, self)
self.next_btn.triggered.connect(lambda: self.navigate_image(1))
self.toolbar.addAction(self.next_btn)
# **Spacer hinzufügen**
spacer = QWidget()
spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
self.toolbar.addWidget(spacer)
# **Neues QLabel für Bildinfos**
self.image_info_label = QLabel(„Keine Bildinformationen“)
self.toolbar.addWidget(self.image_info_label)
def select_initial_directory(self):
„““Lädt das gespeicherte Bildverzeichnis oder fragt den Benutzer, falls keine Konfiguration existiert.“““
# Prüfe, ob ein Verzeichnis in config.txt gespeichert ist
saved_dir = load_config()
if saved_dir and os.path.exists(saved_dir):
self.image_directory = saved_dir
else:
directory = QFileDialog.getExistingDirectory(
self, „Wählen Sie den Bilderordner“, os.path.expanduser(„~“),
options=QFileDialog.Option.ShowDirsOnly
)
if directory:
save_config(directory) # Speichert das Verzeichnis
self.image_directory = directory
else:
self.image_directory = os.path.expanduser(„~“) # Fallback
# Bilder laden
self.update_directory(self.image_directory)
# **NEU: Prüfen, ob Bilder existieren, bevor das erste Bild geladen wird**
if self.image_paths:
self.current_index = 0
self.load_image(self.image_paths[0])
self.update_image_info(self.image_paths[0])
else:
self.image_viewer.setText(„Kein Bild gefunden“)
def change_directory(self):
„““Ändert das Verzeichnis und lädt die Bilder im neuen Verzeichnis.“““
directory = QFileDialog.getExistingDirectory(
self, „Wählen Sie den Bilderordner“,
options=QFileDialog.Option.ShowDirsOnly
)
if directory:
self.image_directory = directory
save_config(directory) # Speichern Sie das neue Verzeichnis in der Konfiguration
self.update_directory(directory) # Aktualisieren Sie die Bildpfade und laden Sie das erste Bild
def update_directory(self, directory):
„““Aktualisiert die Bildpfade basierend auf dem neuen Verzeichnis.“““
self.image_paths = [
os.path.join(directory, f) for f in os.listdir(directory)
if f.lower().endswith((‚.png‘, ‚.jpg‘, ‚.jpeg‘, ‚.gif‘, ‚.bmp‘, ‚.webp‘))
]
if self.image_paths:
self.current_index = 0
self.load_image(self.image_paths[self.current_index])
else:
self.current_index = -1
self.image_viewer.clear()
self.image_viewer.setText(„Kein Bild gefunden“)
# **WICHTIG: Auch die Galerie neu laden**
if hasattr(self, „gallery_widget“):
self.gallery_widget.update_directory(self.image_directory) # **Galerie aktualisieren**
def open_file(self):
„““Öffnet eine Bilddatei direkt.“““
file_path, _ = QFileDialog.getOpenFileName(self, „Bild auswählen“, self.image_directory, „Bilddateien (*.png *.jpg *.jpeg *.gif *.bmp *.webp)“)
if file_path:
self.load_image(file_path)
def toggle_fullscreen(self):
if self.isFullScreen():
self.showNormal()
self.resize(self.normal_size)
self.setFixedSize(1280, 800)
self.image_viewer.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.adjust_image_size(1280, 800)
else:
self.showFullScreen()
self.image_viewer.setAlignment(Qt.AlignmentFlag.AlignTop)
self.adjust_image_size() # Holt nun die Bildschirmgröße selbst
if self.image_paths:
self.load_image(self.image_paths[self.current_index])
def adjust_image_size(self, width=None, height=None):
„““Passt die Bildgröße an die aktuelle Bildschirmgröße an, falls keine Maße übergeben werden.“““
if not self.image_paths:
return
# Falls keine Werte übergeben wurden, Bildschirmgröße abrufen
if width is None or height is None:
screen_size = self.screen().size()
width, height = screen_size.width(), screen_size.height()
current_image_path = self.image_paths[self.current_index]
pixmap = QPixmap(current_image_path)
scaled_pixmap = pixmap.scaled(
width, height,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
self.image_viewer.setPixmap(scaled_pixmap)
def draw_image_info_on_pixmap(self, pixmap, image_path):
from PyQt6.QtGui import QPainter, QColor, QFont, QFontMetrics
from PyQt6.QtCore import Qt, QRect
file_name = os.path.basename(image_path)
file_size = os.path.getsize(image_path) / 1024 # KB
width, height = pixmap.width(), pixmap.height()
image_info = f“{file_name} | {width}x{height} Pixel | {file_size:.2f} KB | Bild {self.current_index + 1}/{len(self.image_paths)}“
modified_pixmap = QPixmap(pixmap) # Kopie des Bildes erstellen
painter = QPainter(modified_pixmap)
# Dynamische Schriftgröße abhängig von der Bildbreite
font = QFont(‚Arial‘, 10)
painter.setFont(font)
font_metrics = QFontMetrics(font)
text_height = font_metrics.height()
# Hintergrund anpassen, damit Text immer sichtbar bleibt
padding = 5
background_height = text_height + 2 * padding
# Falls Bild sehr klein ist, Text immer unten anzeigen
if height < background_height * 2:
background_y = height – background_height – padding # Am unteren Rand
else:
background_y = 0 # Standard: oben
background_rect = QRect(0, background_y, width, background_height)
painter.fillRect(background_rect, QColor(0, 0, 0, 120)) # Transparenter Hintergrund
# Weißen Text zeichnen
painter.setPen(QColor(255, 255, 255))
text_rect = QRect(padding, background_y + padding // 2, width – 2 * padding, text_height)
painter.drawText(text_rect, Qt.AlignmentFlag.AlignLeft, image_info)
painter.end()
return modified_pixmap
def update_image_info(self, image_path):
„““Aktualisiert nur die Bildinformationen im Label.“““
if not image_path or not os.path.exists(image_path):
self.image_info_label.setText(„Keine Bildinformationen“)
return
file_name = os.path.basename(image_path)
file_size = os.path.getsize(image_path) / 1024 # KB
img = QPixmap(image_path).toImage()
width, height = img.width(), img.height()
info_text = f“{file_name} | {width}x{height}px | {file_size:.2f} KB“
self.image_info_label.setText(info_text)
def load_image(self, image_path):
„““Lädt ein Bild und aktualisiert die Anzeige.“““
if not image_path or not os.path.exists(image_path):
self.clear_image_display()
return
pixmap = QPixmap(image_path)
if pixmap.isNull():
self.clear_image_display()
return
# Bildschirmgröße abrufen
if self.isFullScreen():
screen_size = self.screen().size()
target_width, target_height = screen_size.width(), screen_size.height()
else:
target_width = int(max(300, self.image_viewer.width(), self.width()) * 0.98)
target_height = int(max(200, self.image_viewer.height(), self.height()) * 0.98)
scaled_pixmap = pixmap.scaled(
target_width, target_height,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation
)
self._pixmap = scaled_pixmap
self.image_viewer.setPixmap(scaled_pixmap)
self.update_image_info(image_path)
def clear_image_display(self):
„““Setzt die Bildanzeige zurück.“““
self.image_viewer.clear()
self.image_viewer.setText(„Kein Bild geladen“)
self.image_info_label.setText(„Keine Bildinformationen“)
self._pixmap = QPixmap()
def navigate_image(self, direction):
„““Wechselt zum nächsten oder vorherigen Bild.“““
if not self.image_paths:
return
self.current_index = (self.current_index + direction) % len(self.image_paths)
self.load_image(self.image_paths[self.current_index])
def keyPressEvent(self, event: QKeyEvent):
if event.key() == Qt.Key.Key_Right:
self.navigate_image(1) # Vorwärts
elif event.key() == Qt.Key.Key_Left:
self.navigate_image(-1) # Rückwärts
else:
super().keyPressEvent(event)
def save_image(self):
„““Speichert das aktuelle Bild unter demselben Namen.“““
if not self.image_paths or self.current_index == -1:
QMessageBox.warning(self, ‚Fehler‘, ‚Kein Bild zum Speichern.‘)
return
current_image_path = self.image_paths[self.current_index]
pixmap = QPixmap(current_image_path)
if pixmap.isNull():
QMessageBox.warning(self, ‚Fehler‘, ‚Das Bild konnte nicht geladen werden.‘)
return
success = pixmap.save(current_image_path)
if success:
QMessageBox.information(self, ‚Erfolg‘, ‚Bild erfolgreich gespeichert.‘)
else:
QMessageBox.warning(self, ‚Fehler‘, ‚Das Bild konnte nicht gespeichert werden.‘)
def save_image_as(self):
„““Speichert das aktuelle Bild unter einem neuen Namen.“““
if not self.image_paths or self.current_index == -1:
QMessageBox.warning(self, ‚Fehler‘, ‚Kein Bild zum Speichern‘)
return
current_image_path = self.image_paths[self.current_index]
pixmap = QPixmap(current_image_path)
if pixmap.isNull():
QMessageBox.warning(self, ‚Fehler‘, ‚Das Bild konnte nicht geladen werden.‘)
return
file_path, _ = QFileDialog.getSaveFileName(
self,
„Bild speichern unter“,
os.path.dirname(current_image_path),
„Bilddateien (*.png *.jpg *.jpeg *.gif *.bmp *.webp)“
)
if file_path:
success = pixmap.save(file_path)
if success:
QMessageBox.information(self, ‚Erfolg‘, ‚Bild erfolgreich gespeichert.‘)
else:
QMessageBox.warning(self, ‚Fehler‘, ‚Das Bild konnte nicht gespeichert werden.‘)
def save_image_as(self):
„““Speichert das aktuelle Bild unter einem neuen Namen mit vorausgefülltem Dateinamen und aktualisiert die Galerie.“““
if not self.image_paths or self.current_index == -1:
QMessageBox.warning(self, ‚Fehler‘, ‚Kein Bild zum Speichern‘)
return
current_image_path = self.image_paths[self.current_index]
pixmap = QPixmap(current_image_path)
if pixmap.isNull():
QMessageBox.warning(self, ‚Fehler‘, ‚Das Bild konnte nicht geladen werden.‘)
return
# Verzeichnis und Dateiname aus dem aktuellen Bildpfad extrahieren
directory = os.path.dirname(current_image_path)
filename = os.path.basename(current_image_path)
# Speichern-Dialog mit vorausgefülltem Dateinamen
file_path, _ = QFileDialog.getSaveFileName(
self,
„Bild speichern unter“,
os.path.join(directory, filename),
„Bilddateien (*.png *.jpg *.jpeg *.gif *.bmp *.webp)“
)
if file_path:
success = pixmap.save(file_path)
if success:
QMessageBox.information(self, ‚Erfolg‘, ‚Bild erfolgreich gespeichert.‘)
# **Falls ein neuer Name gewählt wurde, aktualisiere die Bildliste**
if file_path != current_image_path:
self.image_paths.append(file_path) # Füge neue Datei zur Bildliste hinzu
self.current_index = self.image_paths.index(file_path) # Setze den Index auf das neue Bild
# **Galerie und Bildverzeichnis aktualisieren**
self.update_directory(self.image_directory)
else:
QMessageBox.warning(self, ‚Fehler‘, ‚Das Bild konnte nicht gespeichert werden.‘)
def delete_image(self):
„““Löscht das aktuelle Bild oder die in der Galerie markierten Bilder.“““
# Galerie-Modus: Markierte Bilder löschen
if self.gallery_widget.selected_images:
reply = QMessageBox.question(
self,
‚Bilder löschen‘,
‚Möchten Sie die ausgewählten Bilder wirklich löschen?‘,
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
for image_path in self.gallery_widget.selected_images:
if os.path.exists(image_path):
try:
os.remove(image_path)
if image_path in self.image_paths:
self.image_paths.remove(image_path)
except Exception as e:
print(f“Fehler beim Löschen von {image_path}: {e}“)
self.gallery_widget.selected_images.clear()
self.gallery_widget.update_directory(self.image_directory)
# Bildanzeige aktualisieren
if self.image_paths:
self.current_index = 0
self.load_image(self.image_paths[0])
else:
self.current_index = -1
self.clear_image_display()
return
# Einzelbild-Modus: Aktuelles Bild löschen
if not self.image_paths or self.current_index == -1:
QMessageBox.warning(self, ‚Fehler‘, ‚Kein Bild zum Löschen vorhanden.‘)
return
current_image_path = self.image_paths[self.current_index]
reply = QMessageBox.question(
self,
‚Bild löschen‘,
‚Möchten Sie das aktuelle Bild wirklich löschen?‘,
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
try:
os.remove(current_image_path)
del self.image_paths[self.current_index]
self.gallery_widget.update_directory(self.image_directory)
if self.image_paths:
self.current_index = min(self.current_index, len(self.image_paths) – 1)
self.load_image(self.image_paths[self.current_index])
else:
self.current_index = -1
self.clear_image_display()
QMessageBox.information(self, ‚Erfolg‘, ‚Bild wurde erfolgreich gelöscht.‘)
except Exception as e:
QMessageBox.warning(self, ‚Fehler‘, f’Fehler beim Löschen des Bildes: {str(e)}‘)
def show_gallery(self):
„““Wechselt zwischen ImageViewer und Galerie und aktualisiert das Bilderverzeichnis.“““
current_sort_criterion = getattr(self, ‚current_sort_criterion‘, ’name‘)
self.update_directory(self.image_directory) # Bildverzeichnis aktualisieren
self.gallery_widget.update_directory(self.image_directory, current_sort_criterion) # Galerie aktualisieren
current_index = self.stacked_widget.currentIndex()
self.stacked_widget.setCurrentIndex(1 if current_index == 0 else 0) # Umschalten
# Toolbar-Button anpassen
if self.stacked_widget.currentIndex() == 1:
self.gallery_btn.setText(„Bildanzeige öffnen“)
else:
self.gallery_btn.setText(„Galerie öffnen“)
def handle_gallery_image_click(self, image_path):
„““Bild aus Galerie anzeigen & zurück zum Viewer wechseln.“““
current_sort_criterion = getattr(self, ‚current_sort_criterion‘, ’name‘)
self.update_directory(self.image_directory) # Bildverzeichnis aktualisieren
self.gallery_widget.update_directory(self.image_directory, current_sort_criterion) # Galerie aktualisieren
if image_path and os.path.exists(image_path):
if image_path in self.image_paths:
self.current_index = self.image_paths.index(image_path)
else:
self.image_paths.append(image_path) # Falls das Bild nicht in der Liste ist
self.current_index = len(self.image_paths) – 1
self.load_image(image_path)
self.stacked_widget.setCurrentIndex(0) # Zurück zur Bildanzeige
def rename_image(self):
„““Benennt das aktuelle Bild um.“““
if not self.image_paths or self.current_index == -1:
QMessageBox.warning(self, „Fehler“, „Kein Bild zum Umbenennen ausgewählt.“)
return
current_image_path = self.image_paths[self.current_index]
directory, old_name = os.path.split(current_image_path)
old_base, old_ext = os.path.splitext(old_name)
new_name, ok = QInputDialog.getText(self, „Bild umbenennen“, „Neuer Name:“, QLineEdit.EchoMode.Normal, old_base)
if ok and new_name:
new_image_path = os.path.join(directory, new_name + old_ext)
# Prüfen, ob die Datei existiert
if os.path.exists(new_image_path):
QMessageBox.warning(self, „Fehler“, „Eine Datei mit diesem Namen existiert bereits.“)
return
try:
os.rename(current_image_path, new_image_path)
# Aktualisiere die Liste der Bilder
self.image_paths[self.current_index] = new_image_path
self.update_image_info(new_image_path)
QMessageBox.information(self, „Erfolg“, „Bild erfolgreich umbenannt.“)
except Exception as e:
QMessageBox.critical(self, „Fehler“, f“Das Bild konnte nicht umbenannt werden: {e}“)
def sort_images(self, criterion=“name“, order=“asc“):
„““Sortiert die Bilder in der Galerie nach dem gewählten Kriterium und Reihenfolge.“““
if not self.image_paths:
return
self.current_sort_criterion = criterion
self.current_sort_order = order # Speichert die aktuelle Reihenfolge
reverse = order == „desc“ # Absteigend sortieren, wenn „desc“
if criterion == „name“:
self.image_paths.sort(key=lambda x: os.path.basename(x).lower(), reverse=reverse)
elif criterion == „date“:
self.image_paths.sort(key=lambda x: os.path.getmtime(x), reverse=reverse)
elif criterion == „size“:
self.image_paths.sort(key=lambda x: os.path.getsize(x), reverse=reverse)
# Galerie aktualisieren
self.gallery_widget.image_paths = self.image_paths
self.gallery_widget.update_directory(self.image_directory, criterion, order)
# Falls ein Bild geladen ist, den Index neu setzen
if self.current_index != -1:
current_image = self.image_paths[self.current_index]
self.current_index = self.image_paths.index(current_image)
def create_sort_menu(self):
„““Erstellt das Sortiermenü für die Bildergalerie.“““
self.sort_menu = self.menuBar().addMenu(„&Sortieren“)
sort_by_name = QAction(„Nach Name sortieren“, self)
sort_by_name.triggered.connect(lambda: self.sort_images(„name“))
self.sort_menu.addAction(sort_by_name)
sort_by_date = QAction(„Nach Datum sortieren“, self)
sort_by_date.triggered.connect(lambda: self.sort_images(„date“))
self.sort_menu.addAction(sort_by_date)
sort_by_size = QAction(„Nach Größe sortieren“, self)
sort_by_size.triggered.connect(lambda: self.sort_images(„size“))
self.sort_menu.addAction(sort_by_size)
def setup_crop_functionality(self):
„““Richtet die Crop-Funktionalität ein.“““
self.crop_widget = ImageCropperWidget(self)
self.stacked_widget.addWidget(self.crop_widget)
# Verbinde das Signal mit dem Handler
self.crop_widget.cropped_image_signal.connect(self.handle_cropped_image)
def handle_cropped_image(self, cropped_pixmap):
„““Behandelt das zugeschnittene Bild.“““
if cropped_pixmap and self.current_index >= 0:
current_path = self.image_paths[self.current_index]
directory = os.path.dirname(current_path)
filename = os.path.basename(current_path)
base_name, ext = os.path.splitext(filename)
# Öffne den Speichern-Dialog
file_path, _ = QFileDialog.getSaveFileName(
self,
„Zugeschnittenes Bild speichern“,
os.path.join(directory, f“{base_name}_cropped{ext}“),
„Bilddateien (*.png *.jpg *.jpeg *.gif *.bmp *.webp)“
)
if file_path:
if cropped_pixmap.save(file_path):
# Füge das neue Bild zur Liste hinzu
self.image_paths.append(file_path)
self.current_index = len(self.image_paths) – 1
self.load_image(file_path)
# Aktualisiere die Galerie
self.gallery_widget.update_directory(self.image_directory)
# Wechsle zurück zur Bildansicht
self.stacked_widget.setCurrentIndex(0)
QMessageBox.information(self, „Erfolg“, „Bild wurde erfolgreich zugeschnitten und gespeichert.“)
else:
QMessageBox.warning(self, „Fehler“, „Das zugeschnittene Bild konnte nicht gespeichert werden.“)
def start_crop_mode(self):
„““Startet den Crop-Modus für das aktuelle Bild.“““
if not self.image_paths or self.current_index == -1:
QMessageBox.warning(self, „Fehler“, „Kein Bild zum Zuschneiden ausgewählt.“)
return
current_image = self.image_paths[self.current_index]
self.crop_widget.load_image_from_path(current_image) # Bild in Crop-Modus laden
self.stacked_widget.setCurrentIndex(2) # Wechselt zur Crop-Ansicht
def resize_current_image(self):
„““Ruft den Bildgrößenänderungs-Dialog aus resize.py auf.“““
if not self.image_paths or self.current_index == -1:
QMessageBox.warning(self, ‚Fehler‘, ‚Kein Bild zum Ändern der Größe.‘)
return
current_image_path = self.image_paths[self.current_index]
resized_path = resize_dialog(self, current_image_path) # Korrigierter Aufruf
if resized_path:
self.image_paths.append(resized_path)
self.current_index = len(self.image_paths) – 1
self.load_image(resized_path)
self.update_directory(self.image_directory)
QMessageBox.information(self, ‚Erfolg‘, ‚Bild erfolgreich geändert.‘)
def exit_crop_mode(self):
„““Beendet den Crop-Modus und kehrt zum Bild-Viewer zurück.“““
# Angenommen, self.crop_widget verweist auf dein Crop-Widget:
if hasattr(self, ‚crop_widget‘):
self.crop_widget.reset_crop_state()
self.stacked_widget.setCurrentIndex(0) # Wechsel zurück zur Bildansicht
def rotate_current_image(self):
„““Dreht das aktuelle Bild.“““
if not self.image_paths or self.current_index == -1:
QMessageBox.warning(self, ‚Fehler‘, ‚Kein Bild zum Drehen ausgewählt.‘)
return
current_image_path = self.image_paths[self.current_index]
current_ext = os.path.splitext(current_image_path)[1].lower()
# Definiere unterstützte Bildformate und ihre Filter
image_formats = {
‚.png‘: ‚PNG (*.png)‘,
‚.jpg‘: ‚JPEG (*.jpg *.jpeg)‘,
‚.jpeg‘: ‚JPEG (*.jpg *.jpeg)‘,
‚.bmp‘: ‚BMP (*.bmp)‘,
‚.gif‘: ‚GIF (*.gif)‘,
‚.webp‘: ‚WebP (*.webp)‘
}
# Erstelle den Filter-String basierend auf dem aktuellen Format
current_format_filter = image_formats.get(current_ext, ‚Alle Bilder (*.*)‘)
filter_string = f“{current_format_filter};;Alle Bilder (*.*)“
try:
rotated_pixmap, angle = show_rotation_dialog(self, current_image_path)
if rotated_pixmap and angle is not None:
directory = os.path.dirname(current_image_path)
filename = os.path.basename(current_image_path)
base_name, ext = os.path.splitext(filename)
suggested_name = f“{base_name}_rotated{ext}“
# Speicherdialog mit vorgeschlagenem Namen und spezifischem Filter
file_path, selected_filter = QFileDialog.getSaveFileName(
self,
„Gedrehtes Bild speichern“,
os.path.join(directory, suggested_name),
filter_string
)
if file_path:
if rotated_pixmap.save(file_path):
self.image_paths.append(file_path)
self.current_index = len(self.image_paths) – 1
self.update_directory(self.image_directory)
self.gallery_widget.update_directory(self.image_directory)
self.load_image(file_path)
self.image_viewer.update()
QMessageBox.information(self, ‚Erfolg‘, f’Bild wurde um {angle}° gedreht.‘)
else:
QMessageBox.warning(self, ‚Fehler‘, ‚Das gedrehte Bild konnte nicht gespeichert werden.‘)
except Exception as e:
QMessageBox.warning(self, ‚Fehler‘, f’Ein Fehler ist aufgetreten: {str(e)}‘)
def main():
app = QApplication(sys.argv)
viewer = ImageViewerApp()
viewer.resize(1280, 800)
viewer.show()
sys.exit(app.exec())
if __name__ == „__main__“:
main()
Mehr Videos findet man auf meinem Youtube Kanal linuxcoach:
https://www.youtube.com/@linuxcoach
Ein weiterer Interessanter Artikel zu diesem Thema:
https://computer-experte.ch/g4music/