first
This commit is contained in:
106
photo_sorter/ui/image_viewer.py
Normal file
106
photo_sorter/ui/image_viewer.py
Normal file
@@ -0,0 +1,106 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user