Files
pyPhotoSorter/photo_sorter/ui/image_viewer.py
Hirnwunde 87ab7ee73c first
2026-02-26 08:42:12 +01:00

107 lines
3.4 KiB
Python

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)