Some optimizations for previous PR

1) Set device pixel ratio on generated QPixmap
2) Avoid copying image data when loading cover from library
3) Convert thumbnail to RGBA in cover thread so as to do less work in
   GUI thread
This commit is contained in:
Kovid Goyal 2024-01-27 09:01:52 +05:30
parent dbc4860ac5
commit f97147afec
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 28 additions and 23 deletions

View File

@ -1708,7 +1708,7 @@ class DB:
return True
return False
def cover_or_cache(self, path, timestamp):
def cover_or_cache(self, path, timestamp, as_what='bytes'):
path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME))
try:
stat = os.stat(path)
@ -1723,7 +1723,13 @@ class DB:
time.sleep(0.2)
f = open(path, 'rb')
with f:
return True, f.read(), stat.st_mtime
if as_what == 'pil_image':
from PIL import Image
data = Image.open(f)
data.load()
else:
data = f.read()
return True, data, stat.st_mtime
def compress_covers(self, path_map, jpeg_quality, progress_callback):
cpath_map = {}

View File

@ -1161,12 +1161,12 @@ class Cache:
return ret
@read_api
def cover_or_cache(self, book_id, timestamp):
def cover_or_cache(self, book_id, timestamp, as_what='bytes'):
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except AttributeError:
return False, None, None
return self.backend.cover_or_cache(path, timestamp)
return self.backend.cover_or_cache(path, timestamp, as_what)
@read_api
def cover_last_modified(self, book_id):

View File

@ -777,10 +777,7 @@ class GridView(QListView):
@property
def device_pixel_ratio(self):
try:
return self.devicePixelRatioF()
except AttributeError:
return self.devicePixelRatio()
@property
def first_visible_row(self):
@ -964,17 +961,16 @@ class GridView(QListView):
cdata, timestamp = tc[book_id] # None, None if not cached.
if timestamp is None:
# Cover not in cache. Try to read the cover from the library.
has_cover, cdata, timestamp = db.new_api.cover_or_cache(book_id, 0)
has_cover, cdata, timestamp = db.new_api.cover_or_cache(book_id, 0, as_what='pil_image')
if has_cover:
# There is a cover.jpg. Convert the byte string to an image.
cache_valid = False
cdata = Image.open(BytesIO(cdata))
else:
# No cover.jpg
cache_valid = None
else:
# A cover is in the cache. Check whether it is up to date.
has_cover, tcdata, timestamp = db.new_api.cover_or_cache(book_id, timestamp)
has_cover, tcdata, timestamp = db.new_api.cover_or_cache(book_id, timestamp, as_what='pil_image')
if has_cover:
if tcdata is None:
# The cached cover is up-to-date.
@ -983,9 +979,6 @@ class GridView(QListView):
else:
# The cached cover is stale
cache_valid = False
# Convert the bytes from the cover.jpg. The image will be
# resized later.
cdata = Image.open(BytesIO(tcdata))
else:
# We found a cached cover for a book without a cover. This can
# happen in older version of calibre that can reuse book_ids
@ -1067,6 +1060,10 @@ class GridView(QListView):
# Return the thumbnail, which is either None or a PIL Image. If not None
# the image will be converted to a QPixmap on the GUI thread. Putting
# None into the CoverCache ensures re-rendering won't try again.
if getattr(thumb, 'mode', None) == 'RGB':
# Conversion to QPixmap needs RGBA data so do it here rather than
# in the GUI thread
thumb = thumb.convert('RGBA')
return thumb
def re_render(self, book_id, thumb):
@ -1076,7 +1073,7 @@ class GridView(QListView):
self.delegate.cover_cache.clear_staging()
if thumb is not None:
# Convert the image to a QPixmap
thumb = convert_PIL_image_to_pixmap(thumb)
thumb = convert_PIL_image_to_pixmap(thumb, self.device_pixel_ratio)
self.delegate.cover_cache.set(book_id, thumb)
m = self.model()
try:

View File

@ -688,10 +688,16 @@ def align8to32(bytes, width, mode):
return b"".join(new_data)
def convert_PIL_image_to_pixmap(im):
def convert_PIL_image_to_pixmap(im, device_pixel_ratio=1.0):
data = None
colortable = None
if im.mode == "1":
if im.mode == "RGBA":
fmt = QImage.Format.Format_RGBA8888
data = im.tobytes("raw", "RGBA")
elif im.mode == "RGB":
fmt = QImage.Format.Format_RGBX8888
data = im.convert("RGBA").tobytes("raw", "RGBA")
elif im.mode == "1":
fmt = QImage.Format.Format_Mono
elif im.mode == "L":
fmt = QImage.Format.Format_Indexed8
@ -700,12 +706,6 @@ def convert_PIL_image_to_pixmap(im):
fmt = QImage.Format.Format_Indexed8
palette = im.getpalette()
colortable = [qRgba(*palette[i : i + 3], 255) & 0xFFFFFFFF for i in range(0, len(palette), 3)]
elif im.mode == "RGB":
fmt = QImage.Format.Format_RGBX8888
data = im.convert("RGBA").tobytes("raw", "RGBA")
elif im.mode == "RGBA":
fmt = QImage.Format.Format_RGBA8888
data = im.tobytes("raw", "RGBA")
elif im.mode == "I;16":
im = im.point(lambda i: i * 256)
fmt = QImage.Format.Format_Grayscale16
@ -715,6 +715,8 @@ def convert_PIL_image_to_pixmap(im):
size = im.size
data = data or align8to32(im.tobytes(), size[0], im.mode)
qimg = QImage(data, size[0], size[1], fmt)
if device_pixel_ratio != 1.0:
qimg.setDevicePixelRatio(device_pixel_ratio)
if colortable:
qimg.setColorTable(colortable)
return QPixmap.fromImage(qimg)