from __future__ import annotations from pathlib import Path from typing import Optional from PyQt6.QtCore import Qt, QSize from PyQt6.QtGui import QPixmap, QImage from PyQt6.QtWidgets import QLabel, QVBoxLayout, QWidget try: from PIL import Image, ExifTags # type: ignore except Exception: # Pillow ist optional Image = None ExifTags = None class ImageViewer(QWidget): """Widget zur Anzeige eines Bildes, skaliert mit korrektem Seitenverhältnis.""" def __init__(self, parent: Optional[QWidget] = None) -> None: super().__init__(parent) self._original_pixmap: Optional[QPixmap] = None self._label = QLabel(self) self._label.setAlignment(Qt.AlignmentFlag.AlignCenter) self._label.setBackgroundRole(self._label.backgroundRole()) self._label.setScaledContents(False) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._label) def clear(self) -> None: self._original_pixmap = None self._label.clear() def _load_pixmap_with_exif(self, path: Path) -> Optional[QPixmap]: if Image is None: pixmap = QPixmap(str(path)) return pixmap if not pixmap.isNull() else None try: img = Image.open(path) # EXIF-Orientation anwenden, falls vorhanden try: exif = img._getexif() # type: ignore[attr-defined] if exif and ExifTags: orientation_key = next( (k for k, v in ExifTags.TAGS.items() if v == "Orientation"), None ) if orientation_key and orientation_key in exif: orientation = exif[orientation_key] if orientation == 3: img = img.rotate(180, expand=True) elif orientation == 6: img = img.rotate(270, expand=True) elif orientation == 8: img = img.rotate(90, expand=True) except Exception: pass img = img.convert("RGBA") data = img.tobytes("raw", "RGBA") qimg = QImage( data, img.width, img.height, QImage.Format.Format_RGBA8888, ) pixmap = QPixmap.fromImage(qimg) return pixmap except Exception: pixmap = QPixmap(str(path)) return pixmap if not pixmap.isNull() else None def set_image(self, path: Optional[Path]) -> None: if path is None: self.clear() if path is None: return pixmap = self._load_pixmap_with_exif(path) if pixmap is None or pixmap.isNull(): self.clear() return self._original_pixmap = pixmap self._update_scaled_pixmap() def resizeEvent(self, event) -> None: # type: ignore[override] super().resizeEvent(event) self._update_scaled_pixmap() def _update_scaled_pixmap(self) -> None: if self._original_pixmap is None: return target_size = self._label.size() if not target_size.isValid(): return scaled = self._original_pixmap.scaled( target_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation, ) self._label.setPixmap(scaled)