From 8a11c9c022eace8e06f4bde0648d7f361f09f55a Mon Sep 17 00:00:00 2001 From: Andy Stewart Date: Sun, 22 Jul 2018 17:28:38 +0800 Subject: [PATCH] Use double-buffer technology to generate pixmap cache when after finger release. --- app/pdfviewer/buffer.py | 71 ++++++++++++++++++++++++++++++++++++++--- core/buffer.py | 2 ++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/app/pdfviewer/buffer.py b/app/pdfviewer/buffer.py index 1f9b542..2967031 100644 --- a/app/pdfviewer/buffer.py +++ b/app/pdfviewer/buffer.py @@ -24,7 +24,9 @@ from PyQt5.QtCore import Qt, QRect from PyQt5.QtGui import QColor, QPixmap, QImage, QFont from PyQt5.QtGui import QPainter from PyQt5.QtWidgets import QWidget +from functools import wraps import fitz +import time from core.buffer import Buffer class AppBuffer(Buffer): @@ -67,6 +69,7 @@ class PdfViewerWidget(QWidget): def __init__(self, url, background_color): super(PdfViewerWidget, self).__init__() + self.url = url self.background_color = background_color # Load document first. @@ -102,6 +105,11 @@ class PdfViewerWidget(QWidget): self.page_cache_pixmap_dict = {} self.page_cache_scale = self.scale self.page_cache_trans = None + self.page_cache_context_delay = 500 + + self.last_action_time = 0 + + self.is_page_just_changed = False def get_page_pixmap(self, index, scale): # Just return cache pixmap when found match index and scale in cache dict. @@ -122,13 +130,22 @@ class PdfViewerWidget(QWidget): self.page_cache_pixmap_dict[index] = qpixmap + print("*** New page pixmap: %s %s" % (self.url, index)) + return qpixmap - def clean_unused_page_cache_pixmap(self, index_list): + def clean_unused_page_cache_pixmap(self): + # We need expand render index bound that avoid clean cache around current index. + start_page_index = max(0, self.get_start_page_index() - 1) + last_page_index = min(self.page_total_number, self.get_last_page_index() + 1) + index_list = list(range(start_page_index, last_page_index)) + + # Try to clean unused cache. cache_index_list = list(self.page_cache_pixmap_dict.keys()) for cache_index in cache_index_list: if cache_index not in index_list: + print("*** Clean unused page pixmap: %s %s" % (self.url, cache_index)) self.page_cache_pixmap_dict.pop(cache_index) def resizeEvent(self, event): @@ -148,15 +165,15 @@ class PdfViewerWidget(QWidget): painter.drawRect(0, 0, self.rect().width(), self.rect().height()) # Get start/last render index. - start_page_index = int(self.scroll_offset * 1.0 / self.scale / self.page_height) - last_page_index = int((self.scroll_offset + self.rect().height()) * 1.0 / self.scale / self.page_height) + start_page_index = self.get_start_page_index() + last_page_index = self.get_last_page_index() # Translate painter at y coordinate. translate_y = (start_page_index * self.scale * self.page_height) - self.scroll_offset painter.translate(0, translate_y) # Render pages in visible area. - for index in list(range(start_page_index, last_page_index + 1)): + for index in list(range(start_page_index, last_page_index)): if index < self.page_total_number: # Get page image. qpixmap = self.get_page_pixmap(index, self.scale) @@ -175,7 +192,7 @@ class PdfViewerWidget(QWidget): painter.drawPixmap(QRect(render_x, render_y, render_width, render_height), qpixmap) # Clean unused pixmap cache that avoid use too much memory. - self.clean_unused_page_cache_pixmap(list(range(start_page_index, last_page_index + 1))) + self.clean_unused_page_cache_pixmap() painter.restore() @@ -189,10 +206,36 @@ class PdfViewerWidget(QWidget): Qt.AlignRight, "{0}% ({1}/{2})".format(int((start_page_index + 1) * 100 / self.page_total_number), start_page_index + 1, self.page_total_number)) + + def build_context_wrap(f): + def wrapper(*args): + # Get self instance object. + self_obj = args[0] + + # Record page before action. + page_before_action = self_obj.get_start_page_index() + + # Do action. + ret = f(*args) + + # Record page after action. + page_after_action = self_obj.get_start_page_index() + self_obj.is_page_just_changed = (page_before_action != page_after_action) + + # Start build context timer. + self_obj.last_action_time = time.time() + QtCore.QTimer().singleShot(self_obj.page_cache_context_delay, self_obj.build_context_cache) + + return ret + + return wrapper + + @build_context_wrap def wheelEvent(self, event): self.scroll_offset = max(min(self.scroll_offset - self.scale * event.angleDelta().y() / 120 * self.mouse_scroll_offset, self.max_scroll_offset()), 0) self.update() + @build_context_wrap def keyPressEvent(self, event): if event.key() == Qt.Key_J: self.scroll_up() @@ -219,6 +262,24 @@ class PdfViewerWidget(QWidget): elif event.key() == Qt.Key_P: self.send_input_message("Jump to percent: ", "jump_percent") + def get_start_page_index(self): + return int(self.scroll_offset * 1.0 / self.scale / self.page_height) + + def get_last_page_index(self): + return int((self.scroll_offset + self.rect().height()) * 1.0 / self.scale / self.page_height) + 1 + + def build_context_cache(self): + # Just build context cache when action duration longer than delay + # Don't build contexnt cache when is_page_just_changed is True, avoid flickr when user change page. + last_action_duration = (time.time() - self.last_action_time) * 1000 + if last_action_duration > self.page_cache_context_delay and not self.is_page_just_changed: + start_page_index = max(0, self.get_start_page_index() - 1) + last_page_index = min(self.page_total_number, self.get_last_page_index() + 1) + + print("*** Try build %s context cache pixmap from %s to %s" % (self.url, start_page_index, last_page_index - 1)) + for index in list(range(start_page_index, last_page_index)): + self.get_page_pixmap(index, self.scale) + def scale_to(self, new_scale): self.scroll_offset = new_scale * 1.0 / self.scale * self.scroll_offset self.scale = new_scale diff --git a/core/buffer.py b/core/buffer.py index 41c5db5..6f4426a 100644 --- a/core/buffer.py +++ b/core/buffer.py @@ -84,6 +84,8 @@ class Buffer(QGraphicsScene): self.buffer_widget.installEventFilter(self) + self.buffer_widget.message_to_emacs = self.message_to_emacs + def handle_destroy(self): self.before_destroy_hook.emit()