openlp/openlp/core/lib/imagemanager.py

266 lines
10 KiB
Python
Raw Normal View History

2010-10-22 18:56:58 +00:00
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
2010-12-26 11:04:47 +00:00
# Copyright (c) 2008-2011 Raoul Snyman #
2011-05-26 16:25:54 +00:00
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
2011-05-26 17:11:22 +00:00
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
2011-06-12 16:02:52 +00:00
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
2011-06-12 15:41:01 +00:00
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
2010-10-22 18:56:58 +00:00
# --------------------------------------------------------------------------- #
# 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
2010-10-23 07:23:49 +00:00
import time
import Queue
2010-10-22 18:56:58 +00:00
2010-10-23 17:37:10 +00:00
from PyQt4 import QtCore
2010-10-22 18:56:58 +00:00
from openlp.core.lib import resize_image, image_to_byte
2011-05-05 11:51:47 +00:00
from openlp.core.ui import ScreenList
2010-10-22 18:56:58 +00:00
log = logging.getLogger(__name__)
class ImageThread(QtCore.QThread):
"""
2011-06-25 05:34:07 +00:00
A special Qt thread class to speed up the display of images. This is
threaded so it loads the frames and generates byte stream in background.
2010-10-22 18:56:58 +00:00
"""
def __init__(self, manager):
QtCore.QThread.__init__(self, None)
2010-12-02 14:37:38 +00:00
self.imageManager = manager
2010-10-22 18:56:58 +00:00
def run(self):
"""
Run the thread.
"""
self.imageManager._process()
class Priority(object):
"""
Enumeration class for different priorities.
2011-06-25 05:34:07 +00:00
``Lowest``
Only the image's byte stream has to be generated. But neither the
``QImage`` nor the byte stream has been requested yet.
``Low``
2011-06-25 05:34:07 +00:00
Only the image's byte stream has to be generated. Because the image's
``QImage`` has been requested previously it is reasonable to assume that
the byte stream will be needed before the byte stream of other images
2011-06-25 06:20:02 +00:00
whose ``QImage`` were not generated due to a request.
``Normal``
The image's byte stream as well as the image has to be generated.
2011-06-25 05:34:07 +00:00
Neither the ``QImage`` nor the byte stream has been requested yet.
``High``
The image's byte stream as well as the image has to be generated. The
2011-06-25 05:34:07 +00:00
``QImage`` for this image has been requested.
**Note**, this priority is only set when the ``QImage`` has not been
generated yet.
``Urgent``
The image's byte stream as well as the image has to be generated. The
byte stream for this image has been requested.
2011-06-25 05:34:07 +00:00
**Note**, this priority is only set when the byte stream has not been
generated yet.
"""
2011-06-25 05:34:07 +00:00
Lowest = 4
Low = 3
Normal = 2
High = 1
Urgent = 0
2010-10-22 18:56:58 +00:00
class Image(object):
2011-06-25 05:34:07 +00:00
"""
This class represents an image. To mark an image as *dirty* set the instance
variables ``image`` and ``image_bytes`` to ``None`` and add the image object
to the queue of images to process.
"""
def __init__(self, name='', path=''):
self.name = name
self.path = path
self.image = None
self.image_bytes = None
self.priority = Priority.Normal
class PriorityQueue(Queue.PriorityQueue):
"""
Customised ``Queue.PriorityQueue``.
"""
2011-07-07 14:53:05 +00:00
def modify_priority(self, image, new_priority):
"""
2011-07-07 14:53:05 +00:00
Modifies the priority of the given ``image``.
2011-07-07 14:53:05 +00:00
``image``
The image to remove. This should be an ``Image`` instance.
2011-07-07 14:53:05 +00:00
``new_priority``
2011-07-07 15:37:43 +00:00
The image's new priority.
"""
2011-07-07 14:53:05 +00:00
self.remove(image)
image.priority = new_priority
self.put((image.priority, image))
def remove(self, image):
"""
Removes the given ``image`` from the queue.
``image``
The image to remove. This should be an ``Image`` instance.
"""
if (image.priority, image) in self.queue:
self.queue.remove((image.priority, image))
2010-10-22 18:56:58 +00:00
2011-02-07 15:55:02 +00:00
2010-10-22 18:56:58 +00:00
class ImageManager(QtCore.QObject):
"""
Image Manager handles the conversion and sizing of images.
"""
log.info(u'Image Manager loaded')
2011-05-05 11:51:47 +00:00
def __init__(self):
2011-02-20 00:05:50 +00:00
QtCore.QObject.__init__(self)
2011-05-05 11:51:47 +00:00
current_screen = ScreenList.get_instance().current
self.width = current_screen[u'size'].width()
self.height = current_screen[u'size'].height()
2010-10-22 18:56:58 +00:00
self._cache = {}
self._imageThread = ImageThread(self)
self._conversion_queue = PriorityQueue()
2010-10-22 18:56:58 +00:00
def update_display(self):
2010-10-22 18:56:58 +00:00
"""
Screen has changed size so rebuild the cache to new size.
2010-10-22 18:56:58 +00:00
"""
log.debug(u'update_display')
current_screen = ScreenList.get_instance().current
self.width = current_screen[u'size'].width()
self.height = current_screen[u'size'].height()
# Mark the images as dirty for a rebuild by setting the image and byte
# stream to None.
self._conversion_queue = PriorityQueue()
for key, image in self._cache.iteritems():
image.priority = Priority.Normal
image.image = None
image.image_bytes = None
self._conversion_queue.put((image.priority, image))
# We want only one thread.
if not self._imageThread.isRunning():
self._imageThread.start()
2010-10-22 18:56:58 +00:00
def get_image(self, name):
2010-10-23 07:23:49 +00:00
"""
Return the ``QImage`` from the cache. If not present wait for the
background thread to process it.
2010-10-23 07:23:49 +00:00
"""
log.debug(u'get_image %s' % name)
image = self._cache[name]
if image.image is None:
2011-07-07 14:53:05 +00:00
self._conversion_queue.modify_priority(image, Priority.High)
while image.image is None:
log.debug(u'get_image - waiting')
time.sleep(0.1)
elif image.image_bytes is None:
# Set the priority to Low, because the image was requested but the
# byte stream was not generated yet. However, we only need to do
# this, when the image was generated before it was requested
# (otherwise this is already taken care of).
self._conversion_queue.modify_priority(image, Priority.Low)
return image.image
2010-10-22 18:56:58 +00:00
def get_image_bytes(self, name):
2010-10-23 07:23:49 +00:00
"""
Returns the byte string for an image. If not present wait for the
background thread to process it.
2010-10-23 07:23:49 +00:00
"""
log.debug(u'get_image_bytes %s' % name)
image = self._cache[name]
if image.image_bytes is None:
2011-07-07 14:53:05 +00:00
self._conversion_queue.modify_priority(image, Priority.Urgent)
while image.image_bytes is None:
2010-10-23 07:23:49 +00:00
log.debug(u'get_image_bytes - waiting')
time.sleep(0.1)
return image.image_bytes
2010-10-22 18:56:58 +00:00
def del_image(self, name):
"""
Delete the Image from the cache.
"""
log.debug(u'del_image %s' % name)
if name in self._cache:
2011-07-07 14:53:05 +00:00
self._conversion_queue.remove(self._cache[name])
del self._cache[name]
2010-10-22 18:56:58 +00:00
def add_image(self, name, path):
"""
Add image to cache if it is not already there.
2010-10-22 18:56:58 +00:00
"""
2010-10-23 07:23:49 +00:00
log.debug(u'add_image %s:%s' % (name, path))
2010-10-22 18:56:58 +00:00
if not name in self._cache:
image = Image(name, path)
2010-10-22 18:56:58 +00:00
self._cache[name] = image
self._conversion_queue.put((image.priority, image))
else:
log.debug(u'Image in cache %s:%s' % (name, path))
# We want only one thread.
if not self._imageThread.isRunning():
self._imageThread.start()
2010-10-22 18:56:58 +00:00
def _process(self):
2010-10-22 18:56:58 +00:00
"""
Controls the processing called from a ``QtCore.QThread``.
2010-10-22 18:56:58 +00:00
"""
log.debug(u'_process - started')
while not self._conversion_queue.empty():
2011-06-25 19:37:23 +00:00
self._process_cache()
log.debug(u'_process - ended')
2010-10-22 18:56:58 +00:00
2011-06-25 19:37:23 +00:00
def _process_cache(self):
2010-10-22 18:56:58 +00:00
"""
Actually does the work.
"""
2011-06-25 19:37:23 +00:00
log.debug(u'_process_cache')
image = self._conversion_queue.get()[1]
# Generate the QImage for the image.
if image.image is None:
image.image = resize_image(image.path, self.width, self.height)
2011-06-25 05:34:07 +00:00
# Set the priority to Lowest and stop here as we need to process
# more important images first.
if image.priority == Priority.Normal:
2011-07-07 14:53:05 +00:00
self._conversion_queue.modify_priority(image, Priority.Lowest)
2011-06-25 05:34:07 +00:00
return
# For image with high priority we set the priority to Low, as the
# byte stream might be needed earlier the byte stream of image with
# Normal priority. We stop here as we need to process more important
# images first.
elif image.priority == Priority.High:
2011-07-07 14:53:05 +00:00
self._conversion_queue.modify_priority(image, Priority.Low)
return
# Generate the byte stream for the image.
if image.image_bytes is None:
image.image_bytes = image_to_byte(image.image)