From c3b890e1989f5ee3f749db4a11a18ecd95f437c4 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 17 Oct 2010 15:43:51 +0100 Subject: [PATCH 1/6] Performance updates --- openlp/core/ui/slidecontroller.py | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 78ac70b86..ba2f0a7b8 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -26,6 +26,7 @@ import logging import os +import time from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon @@ -36,6 +37,24 @@ from openlp.core.lib import OpenLPToolbar, Receiver, resize_image, \ log = logging.getLogger(__name__) +class SlideThread(QtCore.QThread): + """ + A special Qt thread class to speed up the display of text based frames. + This is threaded so it loads the frames in background + """ + def __init__(self, parent, prefix, count): + QtCore.QThread.__init__(self, parent) + self.prefix = prefix + self.count = count + + def run(self): + """ + Run the thread. + """ + time.sleep(1) + for i in range(0, self.count): + Receiver.send_message(u'%s_slide_cache' % self.prefix, i) + class SlideList(QtGui.QTableWidget): """ Customised version of QTableWidget which can respond to keyboard @@ -391,6 +410,8 @@ class SlideController(QtGui.QWidget): if self.isLive: QtCore.QObject.connect(self.volumeSlider, QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'%s_slide_cache' % self.typePrefix), self.slideCache) def screenSizeChanged(self): """ @@ -617,6 +638,10 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.setFocus() Receiver.send_message(u'slidecontroller_%s_started' % self.typePrefix, [serviceItem]) + if self.serviceItem.is_image(): + st = SlideThread( + self, self.typePrefix, len(self.serviceItem.get_frames())) + st.start() def onTextRequest(self): """ @@ -769,6 +794,13 @@ class SlideController(QtGui.QWidget): % self.serviceItem.name.lower(), [self.serviceItem, self.isLive]) + def slideCache(self, slide): + """ + Generate a slide cache item rendered and ready for use + in the background. + """ + self.serviceItem.get_rendered_frame(int(slide)) + def onSlideSelected(self): """ Generate the preview when you click on a slide. From 6dbb80ee9443b1294c90f46d77c7f9f327207282 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Fri, 22 Oct 2010 18:24:56 +0100 Subject: [PATCH 2/6] Initial version of Image performance improvements --- openlp/core/lib/__init__.py | 1 + openlp/core/lib/renderer.py | 21 ++++--------- openlp/core/lib/rendermanager.py | 6 +++- openlp/core/lib/serviceitem.py | 3 +- openlp/core/ui/maindisplay.py | 7 ++--- openlp/core/ui/slidecontroller.py | 41 ++------------------------ openlp/plugins/images/lib/mediaitem.py | 4 +-- 7 files changed, 22 insertions(+), 61 deletions(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index b43c491ff..402b53798 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -314,6 +314,7 @@ def expand_tags(text): from spelltextedit import SpellTextEdit from eventreceiver import Receiver +from imagemanager import ImageManager from settingsmanager import SettingsManager from plugin import PluginStatus, StringContent, Plugin from pluginmanager import PluginManager diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index d5bcab457..b6ed0b322 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -73,7 +73,7 @@ class Renderer(object): self.theme_name = theme.theme_name if theme.background_type == u'image': if theme.background_filename: - self._bg_image_filename = unicode(theme.background_filename) + self._bg_image_filename =theme.background_filename def set_text_rectangle(self, rect_main, rect_footer): """ @@ -118,25 +118,16 @@ class Renderer(object): """ log.debug(u'set frame dest (frame) w %d h %d', frame_width, frame_height) - self.frame = QtGui.QImage(frame_width, frame_height, - QtGui.QImage.Format_ARGB32_Premultiplied) - if self._bg_image_filename and not self.bg_image: - self.bg_image = resize_image(self._bg_image_filename, - self.frame.width(), self.frame.height()) if self._theme.background_type == u'image': - self.bg_frame = QtGui.QImage(self.frame.width(), - self.frame.height(), + frame = QtGui.QImage(frame_width, frame_height, QtGui.QImage.Format_ARGB32_Premultiplied) - painter = QtGui.QPainter() - painter.begin(self.bg_frame) - painter.fillRect(self.frame.rect(), QtCore.Qt.black) - if self.bg_image: - painter.drawImage(0, 0, self.bg_image) - painter.end() - self.bg_image_bytes = image_to_byte(self.bg_frame) + self.bg_image = resize_image(self._bg_image_filename, + frame.width(), frame.height()) + self.bg_image_bytes = image_to_byte(self.bg_image) else: self.bg_frame = None self.bg_image_bytes = None + log.debug(u'end frame dest (frame)') def format_slide(self, words, line_break): """ diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index ec6a7f8cb..da19d59ec 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -28,7 +28,7 @@ import logging from PyQt4 import QtCore -from openlp.core.lib import Renderer, ThemeLevel, ServiceItem +from openlp.core.lib import Renderer, ThemeLevel, ServiceItem, ImageManager from openlp.core.ui import MainDisplay log = logging.getLogger(__name__) @@ -56,6 +56,7 @@ class RenderManager(object): """ log.debug(u'Initilisation started') self.screens = screens + self.image_manager = ImageManager() self.display = MainDisplay(self, screens, False) self.display.setup() self.theme_manager = theme_manager @@ -78,6 +79,7 @@ class RenderManager(object): self.display.setup() self.renderer.bg_frame = None self.themedata = None + self.image_manager.update_display(self.width, self.height) def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global): """ @@ -153,6 +155,8 @@ class RenderManager(object): self.calculate_default(self.screens.current[u'size']) self.renderer.set_theme(self.themedata) self.build_text_rectangle(self.themedata) + self.image_manager.add_image(self.themedata.theme_name, + self.themedata.background_filename) self.renderer.set_frame_dest(self.width, self.height) return self.renderer._rect, self.renderer._rect_footer diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index a5417916e..7eb0d97c9 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -208,6 +208,7 @@ class ServiceItem(object): self.service_item_type = ServiceItemType.Image self._raw_frames.append( {u'title': title, u'image': image, u'path': path}) + self.render_manager.image_manager.add_image(title, path) self._new_item() def add_from_text(self, title, raw_slide, verse_tag=None): @@ -389,7 +390,7 @@ class ServiceItem(object): if self.service_item_type == ServiceItemType.Text: return None, self._display_frames[row][u'html'].split(u'\n')[0] else: - return self._raw_frames[row][u'image'], u'' + return self._raw_frames[row][u'title'], u'' def get_frame_title(self, row=0): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index d8dabd315..7326c0e2f 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -222,7 +222,7 @@ class MainDisplay(DisplayWidget): shrinkItem.resize(self.screen[u'size'].width(), self.screen[u'size'].height()) - def image(self, image): + def image(self, name): """ Add an image as the background. The image is converted to a bytestream on route. @@ -231,9 +231,8 @@ class MainDisplay(DisplayWidget): The Image to be displayed can be QImage or QPixmap """ log.debug(u'image to display') - if not isinstance(image, QtGui.QImage): - image = resize_image(image, self.screen[u'size'].width(), - self.screen[u'size'].height()) + image = self.parent.parent.RenderManager.image_manager. \ + get_image(name) self.resetVideo() self.displayImage(image) # show screen diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index ba2f0a7b8..d44ff226c 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -37,24 +37,6 @@ from openlp.core.lib import OpenLPToolbar, Receiver, resize_image, \ log = logging.getLogger(__name__) -class SlideThread(QtCore.QThread): - """ - A special Qt thread class to speed up the display of text based frames. - This is threaded so it loads the frames in background - """ - def __init__(self, parent, prefix, count): - QtCore.QThread.__init__(self, parent) - self.prefix = prefix - self.count = count - - def run(self): - """ - Run the thread. - """ - time.sleep(1) - for i in range(0, self.count): - Receiver.send_message(u'%s_slide_cache' % self.prefix, i) - class SlideList(QtGui.QTableWidget): """ Customised version of QTableWidget which can respond to keyboard @@ -410,8 +392,6 @@ class SlideController(QtGui.QWidget): if self.isLive: QtCore.QObject.connect(self.volumeSlider, QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume) - QtCore.QObject.connect(Receiver.get_receiver(), - QtCore.SIGNAL(u'%s_slide_cache' % self.typePrefix), self.slideCache) def screenSizeChanged(self): """ @@ -606,13 +586,9 @@ class SlideController(QtGui.QWidget): label = QtGui.QLabel() label.setMargin(4) label.setScaledContents(True) - if isinstance(frame[u'image'], QtGui.QImage): - label.setPixmap(QtGui.QPixmap.fromImage(frame[u'image'])) - else: - pixmap = resize_image(frame[u'image'], - self.parent.RenderManager.width, - self.parent.RenderManager.height) - label.setPixmap(QtGui.QPixmap.fromImage(pixmap)) + image = self.parent.RenderManager.image_manager. \ + get_image(frame[u'title']) + label.setPixmap(QtGui.QPixmap.fromImage(image)) self.PreviewListWidget.setCellWidget(framenumber, 0, label) slideHeight = width * self.parent.RenderManager.screen_ratio row += 1 @@ -638,10 +614,6 @@ class SlideController(QtGui.QWidget): self.PreviewListWidget.setFocus() Receiver.send_message(u'slidecontroller_%s_started' % self.typePrefix, [serviceItem]) - if self.serviceItem.is_image(): - st = SlideThread( - self, self.typePrefix, len(self.serviceItem.get_frames())) - st.start() def onTextRequest(self): """ @@ -794,13 +766,6 @@ class SlideController(QtGui.QWidget): % self.serviceItem.name.lower(), [self.serviceItem, self.isLive]) - def slideCache(self, slide): - """ - Generate a slide cache item rendered and ready for use - in the background. - """ - self.serviceItem.get_rendered_frame(int(slide)) - def onSlideSelected(self): """ Generate the preview when you click on a slide. diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 5a3918dd9..05510fc13 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -166,9 +166,9 @@ class ImageMediaItem(MediaManagerItem): for item in items: bitem = self.listView.item(item.row()) filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) - frame = QtGui.QImage(unicode(filename)) + #frame = QtGui.QImage(unicode(filename)) (path, name) = os.path.split(filename) - service_item.add_from_image(path, name, frame) + service_item.add_from_image(path, name, None) return True else: return False From 76ddf975a76fa6365f3ebfaea56143d89690c358 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Fri, 22 Oct 2010 19:56:58 +0100 Subject: [PATCH 3/6] Add ImageManager --- openlp/core/lib/imagemanager.py | 145 ++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 openlp/core/lib/imagemanager.py diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py new file mode 100644 index 000000000..83277ad6e --- /dev/null +++ b/openlp/core/lib/imagemanager.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # +# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Provides the store and management for Images automatically caching them and +resizing them when needed. Only one copy of each image is needed in the system. +A Thread is used to convert the image to a byte array so the user does not need +to wait for the conversion to happen. +""" +import logging +import os + +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import resize_image, image_to_byte + +log = logging.getLogger(__name__) + +class ImageThread(QtCore.QThread): + """ + A special Qt thread class to speed up the display of text based frames. + This is threaded so it loads the frames in background + """ + def __init__(self, manager): + QtCore.QThread.__init__(self, None) + self.image_mamager = manager + + def run(self): + """ + Run the thread. + """ + self.image_mamager.process() + + +class Image(object): + name = '' + path = '' + dirty = True + image = None + image_bytes = None + +class ImageManager(QtCore.QObject): + """ + Image Manager handles the conversion and sizing of images. + + """ + log.info(u'Image Manager loaded') + + def __init__(self): + self._cache = {} + self._thread_running = False + self._cache_dirty = False + self.image_thread = ImageThread(self) + + def update_display(self, width, height): + """ + Screen has changed size so rebuild the cache to new size + """ + log.debug(u'update_display') + self.width = width + self.height = height + # mark the images as dirty for a rebuild + for key in self._cache.keys(): + image = self._cache[key] + image.dirty = True + fullpath = os.path.join(image.path, image.name) + image.image = resize_image(fullpath, + self.width, self.height) + self._cache_dirty = True + # only one thread please + if not self._thread_running: + self.image_thread.start() + + def get_image(self, name): + return self._cache[name].image + + def get_image_bytes(self, name): + return self._cache[name].image_bytes + + def add_image(self, name, path): + """ + Add image to cache if it is not already there + """ + log.debug(u'add_image') + if not name in self._cache: + image = Image() + image.name = name + image.path = path + fullpath = os.path.join(image.path, image.name) + image.image = resize_image(fullpath, + self.width, self.height) + self._cache[name] = image + self._cache_dirty = True + # only one thread please + if not self._thread_running: + self.image_thread.start() + + def process(self): + """ + Controls the processing called from a QThread + """ + log.debug(u'process - started') + self._thread_running = True + self.clean_cache() + # data loaded since we started ? + while self._cache_dirty: + log.debug(u'process - recycle') + self.clean_cache() + self._thread_running = False + log.debug(u'process - ended') + + def clean_cache(self): + """ + Actually does the work. + """ + log.debug(u'clean_cache') + # we will clean the cache now + self._cache_dirty = False + for key in self._cache.keys(): + image = self._cache[key] + if image.dirty: + image.image_bytes = image_to_byte(image.image) + image.dirty = False From b0ba4472bd1c28ca9d2282299241a144ce02bc11 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 23 Oct 2010 08:23:49 +0100 Subject: [PATCH 4/6] Fix up themes now --- openlp/core/lib/htmlbuilder.py | 13 +++++----- openlp/core/lib/imagemanager.py | 19 +++++++++++--- openlp/core/lib/renderer.py | 36 -------------------------- openlp/core/lib/rendermanager.py | 1 - openlp/core/lib/serviceitem.py | 1 - openlp/core/ui/maindisplay.py | 19 +++++++++----- openlp/plugins/images/lib/mediaitem.py | 6 ++--- 7 files changed, 39 insertions(+), 56 deletions(-) diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index 511046fe3..dc3a48565 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -90,16 +90,16 @@ body { var transition = %s; function show_video(state, path, volume, loop){ - // Note, the preferred method for looping would be to use the + // Note, the preferred method for looping would be to use the // video tag loop attribute. // But QtWebKit doesn't support this. Neither does it support the // onended event, hence the setInterval() // In addition, setting the currentTime attribute to zero to restart // the video raises an INDEX_SIZE_ERROR: DOM Exception 1 - // To complicate it further, sometimes vid.currentTime stops + // To complicate it further, sometimes vid.currentTime stops // slightly short of vid.duration and vid.ended is intermittent! // - // Note, currently the background may go black between loops. Not + // Note, currently the background may go black between loops. Not // desirable. Need to investigate using two