107 lines
3.4 KiB
Python
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)
|