openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #04268
[Merge] lp:~trb143/openlp/images into lp:openlp
Tim Bentley has proposed merging lp:~trb143/openlp/images into lp:openlp.
Requested reviews:
OpenLP Core (openlp-core)
Changes to Image processing to improve performance.
Moved image storage for Themes and Image plugins to central cache.
Moved conversion to byte array on to a thread
Built basic thread locking management to cover adding and processing at same time
Presentations and Videos work how that always did.
Cleaned up lots of image size and type conversions.
--
https://code.launchpad.net/~trb143/openlp/images/+merge/39218
Your team OpenLP Core is requested to review the proposed merge of lp:~trb143/openlp/images into lp:openlp.
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py 2010-10-15 15:37:29 +0000
+++ openlp/core/lib/__init__.py 2010-10-24 06:46:49 +0000
@@ -314,6 +314,7 @@
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
=== modified file 'openlp/core/lib/htmlbuilder.py'
--- openlp/core/lib/htmlbuilder.py 2010-10-07 21:49:25 +0000
+++ openlp/core/lib/htmlbuilder.py 2010-10-24 06:46:49 +0000
@@ -90,16 +90,16 @@
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 <video>'s, and hiding/
// preloading one, and toggle between the two when looping.
@@ -132,8 +132,8 @@
vid.style.visibility = 'visible';
if(vid.looping){
video_timer = setInterval(
- function() {
- show_video('poll');
+ function() {
+ show_video('poll');
}, 200);
}
break;
@@ -328,6 +328,7 @@
height = screen[u'size'].height()
theme = item.themedata
webkitvers = webkit_version()
+ # Image generated and poked in
if item.bg_image_bytes:
image = u'src="data:image/png;base64,%s"' % item.bg_image_bytes
else:
@@ -455,7 +456,7 @@
if theme.display_outline and webkitvers < 534.3:
shadow = u'padding-left: %spx; padding-top: %spx;' % \
(int(theme.display_shadow_size) +
- (int(theme.display_outline_size) * 2),
+ (int(theme.display_outline_size) * 2),
theme.display_shadow_size)
shadow += build_lyrics_outline_css(theme, True)
else:
=== added file 'openlp/core/lib/imagemanager.py'
--- openlp/core/lib/imagemanager.py 1970-01-01 00:00:00 +0000
+++ openlp/core/lib/imagemanager.py 2010-10-24 06:46:49 +0000
@@ -0,0 +1,158 @@
+# -*- 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
+import time
+
+from PyQt4 import QtCore
+
+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 the Qimage from the cache
+ """
+ log.debug(u'get_image %s' % name)
+ return self._cache[name].image
+
+ def get_image_bytes(self, name):
+ """
+ Returns the byte string for an image
+ If not present wait for the background thread to process it.
+ """
+ log.debug(u'get_image_bytes %s' % name)
+ if not self._cache[name].image_bytes:
+ while self._cache[name].dirty:
+ log.debug(u'get_image_bytes - waiting')
+ time.sleep(0.1)
+ 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 %s:%s' % (name, path))
+ if not name in self._cache:
+ image = Image()
+ image.name = name
+ image.path = path
+ image.image = resize_image(path,
+ 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
=== modified file 'openlp/core/lib/renderer.py'
--- openlp/core/lib/renderer.py 2010-09-29 20:39:15 +0000
+++ openlp/core/lib/renderer.py 2010-10-24 06:46:49 +0000
@@ -51,11 +51,6 @@
self._rect = None
self.theme_name = None
self._theme = None
- self._bg_image_filename = None
- self.frame = None
- self.bg_frame = None
- self.bg_image = None
- self.bg_image_bytes = None
def set_theme(self, theme):
"""
@@ -66,14 +61,7 @@
"""
log.debug(u'set theme')
self._theme = theme
- self.bg_frame = None
- self.bg_image = None
- self.bg_image_bytes = None
- self._bg_image_filename = None
self.theme_name = theme.theme_name
- if theme.background_type == u'image':
- if theme.background_filename:
- self._bg_image_filename = unicode(theme.background_filename)
def set_text_rectangle(self, rect_main, rect_footer):
"""
@@ -105,39 +93,6 @@
(build_lyrics_format_css(self._theme, self.page_width,
self.page_height), build_lyrics_outline_css(self._theme))
- def set_frame_dest(self, frame_width, frame_height):
- """
- Set the size of the slide.
-
- ``frame_width``
- The width of the slide.
-
- ``frame_height``
- The height of the slide.
-
- """
- 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(),
- 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)
- else:
- self.bg_frame = None
- self.bg_image_bytes = None
-
def format_slide(self, words, line_break):
"""
Figure out how much text can appear on a slide, using the current
=== modified file 'openlp/core/lib/rendermanager.py'
--- openlp/core/lib/rendermanager.py 2010-09-27 18:34:40 +0000
+++ openlp/core/lib/rendermanager.py 2010-10-24 06:46:49 +0000
@@ -28,7 +28,7 @@
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,7 +56,9 @@
"""
log.debug(u'Initilisation started')
self.screens = screens
+ self.image_manager = ImageManager()
self.display = MainDisplay(self, screens, False)
+ self.display.imageManager = self.image_manager
self.display.setup()
self.theme_manager = theme_manager
self.renderer = Renderer()
@@ -75,9 +77,11 @@
log.debug(u'Update Display')
self.calculate_default(self.screens.current[u'size'])
self.display = MainDisplay(self, self.screens, False)
+ self.display.imageManager = self.image_manager
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,7 +157,8 @@
self.calculate_default(self.screens.current[u'size'])
self.renderer.set_theme(self.themedata)
self.build_text_rectangle(self.themedata)
- self.renderer.set_frame_dest(self.width, self.height)
+ self.image_manager.add_image(self.themedata.theme_name,
+ self.themedata.background_filename)
return self.renderer._rect, self.renderer._rect_footer
def build_text_rectangle(self, theme):
@@ -211,7 +216,7 @@
serviceItem.raw_footer = footer
serviceItem.render(True)
self.display.buildHtml(serviceItem)
- raw_html = serviceItem.get_rendered_frame(0)[1]
+ raw_html = serviceItem.get_rendered_frame(0)
preview = self.display.text(raw_html)
# Reset the real screen size for subsequent render requests
self.calculate_default(self.screens.current[u'size'])
=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py 2010-10-15 08:35:00 +0000
+++ openlp/core/lib/serviceitem.py 2010-10-24 06:46:49 +0000
@@ -30,7 +30,6 @@
import logging
import os
-import time
import uuid
from PyQt4 import QtGui
@@ -160,12 +159,10 @@
theme = self.theme
self.main, self.footer = \
self.render_manager.set_override_theme(theme, useOverride)
- self.bg_image_bytes = self.render_manager.renderer.bg_image_bytes
self.themedata = self.render_manager.renderer._theme
if self.service_item_type == ServiceItemType.Text:
log.debug(u'Formatting slides')
for slide in self._raw_frames:
- before = time.time()
formatted = self.render_manager \
.format_slide(slide[u'raw_slide'], line_break)
for page in formatted:
@@ -174,12 +171,8 @@
u'text': clean_tags(page.rstrip()),
u'html': expand_tags(page.rstrip()),
u'verseTag': slide[u'verseTag'] })
- log.log(15, u'Formatting took %4s' % (time.time() - before))
- elif self.service_item_type == ServiceItemType.Image:
- for slide in self._raw_frames:
- slide[u'image'] = resize_image(slide[u'image'],
- self.render_manager.width, self.render_manager.height)
- elif self.service_item_type == ServiceItemType.Command:
+ elif self.service_item_type == ServiceItemType.Image or \
+ self.service_item_type == ServiceItemType.Command:
pass
else:
log.error(u'Invalid value renderer :%s' % self.service_item_type)
@@ -192,7 +185,7 @@
else:
self.foot_text = u'%s<br>%s' % (self.foot_text, foot)
- def add_from_image(self, path, title, image):
+ def add_from_image(self, path, title):
"""
Add an image slide to the service item.
@@ -201,13 +194,11 @@
``title``
A title for the slide in the service item.
-
- ``image``
- The actual image file name.
"""
self.service_item_type = ServiceItemType.Image
self._raw_frames.append(
- {u'title': title, u'image': image, u'path': path})
+ {u'title': title, 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):
@@ -241,7 +232,7 @@
"""
self.service_item_type = ServiceItemType.Command
self._raw_frames.append(
- {u'title': file_name, u'image': image, u'path': path})
+ {u'title': file_name, u'image':image, u'path': path})
self._new_item()
def get_service_repr(self):
@@ -310,8 +301,7 @@
elif self.service_item_type == ServiceItemType.Image:
for text_image in serviceitem[u'serviceitem'][u'data']:
filename = os.path.join(path, text_image)
- real_image = QtGui.QImage(unicode(filename))
- self.add_from_image(path, text_image, real_image)
+ self.add_from_image(filename, text_image)
elif self.service_item_type == ServiceItemType.Command:
for text_image in serviceitem[u'serviceitem'][u'data']:
filename = os.path.join(path, text_image[u'title'])
@@ -387,9 +377,11 @@
renders it if required.
"""
if self.service_item_type == ServiceItemType.Text:
- return None, self._display_frames[row][u'html'].split(u'\n')[0]
+ return self._display_frames[row][u'html'].split(u'\n')[0]
+ elif self.service_item_type == ServiceItemType.Image:
+ return self._raw_frames[row][u'title']
else:
- return self._raw_frames[row][u'image'], u''
+ return self._raw_frames[row][u'image']
def get_frame_title(self, row=0):
"""
=== modified file 'openlp/core/ui/maindisplay.py'
--- openlp/core/ui/maindisplay.py 2010-10-17 14:25:03 +0000
+++ openlp/core/ui/maindisplay.py 2010-10-24 06:46:49 +0000
@@ -225,7 +225,14 @@
shrinkItem.resize(self.screen[u'size'].width(),
self.screen[u'size'].height())
- def image(self, image):
+ def directImage(self, name, path):
+ """
+ API for replacement backgrounds so Images are added directly to cache
+ """
+ image = self.imageManager.add_image(name, path)
+ self.image(name)
+
+ def image(self, name):
"""
Add an image as the background. The image is converted to a
bytestream on route.
@@ -234,25 +241,20 @@
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.imageManager.get_image_bytes(name)
self.resetVideo()
self.displayImage(image)
# show screen
if self.isLive:
self.setVisible(True)
+ return self.preview()
def displayImage(self, image):
"""
Display an image, as is.
"""
if image:
- if isinstance(image, QtGui.QImage):
- js = u'show_image("data:image/png;base64,%s");' % \
- image_to_byte(image)
- else:
- js = u'show_image("data:image/png;base64,%s");' % image
+ js = u'show_image("data:image/png;base64,%s");' % image
else:
js = u'show_image("");'
self.frame.evaluateJavaScript(js)
@@ -399,6 +401,9 @@
self.loaded = False
self.initialFrame = False
self.serviceItem = serviceItem
+ if self.serviceItem.themedata.background_filename:
+ self.serviceItem.bg_image_bytes = self.imageManager. \
+ get_image_bytes(self.serviceItem.themedata.theme_name)
html = build_html(self.serviceItem, self.screen, self.parent.alertTab,
self.isLive)
log.debug(u'buildHtml - pre setHtml')
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2010-10-17 07:26:14 +0000
+++ openlp/core/ui/slidecontroller.py 2010-10-24 06:46:49 +0000
@@ -26,6 +26,7 @@
import logging
import os
+import time
from PyQt4 import QtCore, QtGui
from PyQt4.phonon import Phonon
@@ -400,6 +401,7 @@
log.debug(u'screenSizeChanged live = %s' % self.isLive)
# rebuild display as screen size changed
self.display = MainDisplay(self, self.screens, self.isLive)
+ self.display.imageManager = self.parent.RenderManager.image_manager
self.display.alertTab = self.alertTab
self.ratio = float(self.screens.current[u'size'].width()) / \
float(self.screens.current[u'size'].height())
@@ -585,13 +587,14 @@
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'],
+ if self.serviceItem.is_command():
+ image = resize_image(frame[u'image'],
self.parent.RenderManager.width,
self.parent.RenderManager.height)
- label.setPixmap(QtGui.QPixmap.fromImage(pixmap))
+ else:
+ 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
@@ -782,15 +785,12 @@
[self.serviceItem, self.isLive, row])
self.updatePreview()
else:
- frame, raw_html = self.serviceItem.get_rendered_frame(row)
+ toDisplay = self.serviceItem.get_rendered_frame(row)
if self.serviceItem.is_text():
- frame = self.display.text(raw_html)
- else:
- self.display.image(frame)
- if isinstance(frame, QtGui.QImage):
- self.SlidePreview.setPixmap(QtGui.QPixmap.fromImage(frame))
- else:
- self.SlidePreview.setPixmap(QtGui.QPixmap(frame))
+ frame = self.display.text(toDisplay)
+ else:
+ frame = self.display.image(toDisplay)
+ self.SlidePreview.setPixmap(QtGui.QPixmap.fromImage(frame))
self.selectedRow = row
Receiver.send_message(u'slidecontroller_%s_changed' % self.typePrefix,
row)
=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py 2010-09-27 18:34:40 +0000
+++ openlp/plugins/images/lib/mediaitem.py 2010-10-24 06:46:49 +0000
@@ -166,9 +166,8 @@
for item in items:
bitem = self.listView.item(item.row())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
- frame = QtGui.QImage(unicode(filename))
(path, name) = os.path.split(filename)
- service_item.add_from_image(path, name, frame)
+ service_item.add_from_image(filename, name)
return True
else:
return False
@@ -185,7 +184,8 @@
for item in items:
bitem = self.listView.item(item.row())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
- self.parent.liveController.display.image(filename)
+ (path, name) = os.path.split(filename)
+ self.parent.liveController.display.directImage(name, filename)
self.resetButton.setVisible(True)
def onPreviewClick(self):
Follow ups