Perf: PDF-Thumbnails progressiv rendern statt alle auf einmal

Placeholder-Labels werden sofort erstellt, das eigentliche Rendern
erfolgt asynchron über QTimer.singleShot(0) — ein Thumbnail pro
Event-Loop-Iteration. UI friert nicht mehr ein; RAM-Spitze wird verteilt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 20:19:42 +01:00
parent 87b2d9273f
commit 29574ce0dc
+32 -24
View File
@@ -14,7 +14,7 @@ import logging
import time import time
from pathlib import Path from pathlib import Path
from PySide6.QtCore import Qt, QSize, QUrl from PySide6.QtCore import Qt, QSize, QTimer, QUrl
from PySide6.QtGui import QCursor, QPixmap, QPainter, QDesktopServices from PySide6.QtGui import QCursor, QPixmap, QPainter, QDesktopServices
from PySide6.QtWidgets import QLabel, QMessageBox, QSpacerItem, QSizePolicy from PySide6.QtWidgets import QLabel, QMessageBox, QSpacerItem, QSizePolicy
from PySide6.QtPdf import QPdfDocument from PySide6.QtPdf import QPdfDocument
@@ -206,6 +206,27 @@ class PdfViewerMixin:
painter.end() painter.end()
return result return result
def _schedule_thumbnail_rendering(self, diff_doc, pdf_basename, thumbnail_labels, page_num):
"""Rendert Thumbnails progressiv — ein Thumbnail pro Event-Loop-Iteration."""
if page_num >= len(thumbnail_labels):
return
thumbnail = thumbnail_labels[page_num]
# Prüfe ob das Label noch existiert (Benutzer könnte inzwischen anderes PDF geöffnet haben)
if thumbnail_labels[page_num] not in self.thumbnail_to_page:
return
page_size = diff_doc.pagePointSize(page_num)
scale_factor = 200.0 / page_size.width()
page_image = diff_doc.render(
page_num,
QSize(int(page_size.width() * scale_factor), int(page_size.height() * scale_factor)),
)
thumbnail.setPixmap(QPixmap.fromImage(page_image).scaledToWidth(200, Qt.TransformationMode.SmoothTransformation))
thumbnail.setMinimumHeight(0)
QTimer.singleShot(0, lambda: self._schedule_thumbnail_rendering(diff_doc, pdf_basename, thumbnail_labels, page_num + 1))
def _clear_layout(self, layout): def _clear_layout(self, layout):
"""Entfernt alle Widgets aus einem Layout.""" """Entfernt alle Widgets aus einem Layout."""
if layout is not None: if layout is not None:
@@ -395,43 +416,30 @@ class PdfViewerMixin:
# Nehme die Seitenzahl der diff-PDF als Basis # Nehme die Seitenzahl der diff-PDF als Basis
max_pages = diff_doc.pageCount() max_pages = diff_doc.pageCount()
# Erstelle Thumbnails für alle Seiten # Erstelle Placeholder-Labels für alle Seiten (sofort, ohne Rendern)
thumbnail_labels = []
for page_num in range(max_pages): for page_num in range(max_pages):
# Nur diff-Seite für Thumbnail rendern
page_size = diff_doc.pagePointSize(page_num)
# Skalierung für Thumbnail
scale_factor = 200.0 / page_size.width() # 200 Pixel Breite
# Seite rendern
page_image = diff_doc.render(
page_num,
QSize(int(page_size.width() * scale_factor), int(page_size.height() * scale_factor)),
)
diff_pixmap = QPixmap.fromImage(page_image)
# Thumbnail erstellen und zur linken Spalte hinzufügen
thumbnail = QLabel() thumbnail = QLabel()
thumbnail.setObjectName(f"thumbnail_{pdf_basename}_page_{page_num + 1}") thumbnail.setObjectName(f"thumbnail_{pdf_basename}_page_{page_num + 1}")
thumbnail.setPixmap(diff_pixmap.scaledToWidth(200, Qt.TransformationMode.SmoothTransformation)) thumbnail.setText(f"Seite {page_num + 1}\n")
thumbnail.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) thumbnail.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
thumbnail.setMouseTracking(True) thumbnail.setMouseTracking(True)
thumbnail.setAlignment(Qt.AlignmentFlag.AlignCenter) thumbnail.setAlignment(Qt.AlignmentFlag.AlignCenter)
thumbnail.setMinimumHeight(150)
self.ui.verticalLayout_2.addWidget(thumbnail) self.ui.verticalLayout_2.addWidget(thumbnail)
# Seitennummer für Thumbnail anzeigen
thumbnail_info = QLabel(f"Seite {page_num + 1}") thumbnail_info = QLabel(f"Seite {page_num + 1}")
thumbnail_info.setAlignment(Qt.AlignmentFlag.AlignCenter) thumbnail_info.setAlignment(Qt.AlignmentFlag.AlignCenter)
thumbnail_info.setMaximumHeight(18) # Kompakte Höhe thumbnail_info.setMaximumHeight(18)
thumbnail_info.setContentsMargins(0, 0, 0, 0) # Keine Ränder thumbnail_info.setContentsMargins(0, 0, 0, 0)
self.ui.verticalLayout_2.addWidget(thumbnail_info) self.ui.verticalLayout_2.addWidget(thumbnail_info)
# Beziehung zwischen Thumbnail und Seitennummer speichern
self.thumbnail_to_page[thumbnail] = {"pdf_filename": pdf_basename, "page_num": page_num} self.thumbnail_to_page[thumbnail] = {"pdf_filename": pdf_basename, "page_num": page_num}
# Click-Event für das Thumbnail einrichten
thumbnail.mousePressEvent = lambda event, t=thumbnail: self.on_thumbnail_clicked(event, t) thumbnail.mousePressEvent = lambda event, t=thumbnail: self.on_thumbnail_clicked(event, t)
thumbnail_labels.append(thumbnail)
# Thumbnails progressiv rendern (nicht blockierend)
self._schedule_thumbnail_rendering(diff_doc, pdf_basename, thumbnail_labels, 0)
# Füge expandierenden Spacer am Ende hinzu, damit Thumbnails oben bleiben # Füge expandierenden Spacer am Ende hinzu, damit Thumbnails oben bleiben
spacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) spacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)