[gnome-music/wip/mschraal/coverart: 4/5] Introduce HiDPI support



commit 576911b970eee8f99f6979813e79f282a6c3e7c0
Author: Marinus Schraal <mschraal src gnome org>
Date:   Tue Sep 27 14:47:43 2016 +0200

    Introduce HiDPI support
    
    Use cairo surfaces to support HiDPI artwork.
    Search stil requires us to convert back to GdkPixbuf at the end of the
    line, because of libgd limitations.

 gnomemusic/albumartcache.py |   52 +++++++++++++++++------------
 gnomemusic/player.py        |   15 ++++----
 gnomemusic/view.py          |   76 +++++++++++++++++++++++++++++++-----------
 gnomemusic/widgets.py       |   54 ++++++++++++++++++------------
 4 files changed, 128 insertions(+), 69 deletions(-)
---
diff --git a/gnomemusic/albumartcache.py b/gnomemusic/albumartcache.py
index d562557..142b251 100644
--- a/gnomemusic/albumartcache.py
+++ b/gnomemusic/albumartcache.py
@@ -47,13 +47,14 @@ logger = logging.getLogger(__name__)
 
 
 @log
-def _make_icon_frame(pixbuf):
-    border = 3
+def _make_icon_frame(pixbuf, art_size=None, scale=1):
+    border = 3 * scale
     degrees = pi / 180
-    radius = 3
+    radius = 3 * scale
 
-    w = pixbuf.get_width()
-    h = pixbuf.get_height()
+    ratio = pixbuf.get_height() / pixbuf.get_width()
+    w = art_size.width * scale
+    h = int(art_size.height * ratio * scale)
 
     new_pixbuf = pixbuf.scale_simple(w - border * 2,
                                      h - border * 2,
@@ -83,7 +84,11 @@ def _make_icon_frame(pixbuf):
 
     border_pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h)
 
-    return border_pixbuf
+    surface = Gdk.cairo_surface_create_from_pixbuf(border_pixbuf,
+                                                   scale,
+                                                   None)
+
+    return surface
 
 
 class ArtSize(Enum):
@@ -108,14 +113,20 @@ class DefaultIcon(GObject.GObject):
         music = 'folder-music-symbolic'
 
     _cache = {}
+    _scale = 1
 
     def __repr__(self):
         return '<DefaultIcon>'
 
     @log
-    def _make_default_icon(self, icon_type, art_size):
-        width = art_size.width
-        height = art_size.height
+    def __init__(self, scale=1):
+        GObject.GObject.__init__(self)
+        self._scale = scale
+
+    @log
+    def _make_default_icon(self, icon_type, art_size=None):
+        width = art_size.width * self._scale
+        height = art_size.height * self._scale
 
         icon = Gtk.IconTheme.get_default().load_icon(icon_type.value,
                                                      max(width, height) / 4,
@@ -138,9 +149,9 @@ class DefaultIcon(GObject.GObject):
                        icon.get_height() * 3 / 2,
                        1, 1, GdkPixbuf.InterpType.HYPER, 0x33)
 
-        final_icon = _make_icon_frame(result)
+        icon_surface = _make_icon_frame(result, art_size, self._scale)
 
-        return final_icon
+        return icon_surface
 
     @log
     def get(self, icon_type, art_size):
@@ -170,13 +181,15 @@ class AlbumArtCache(GObject.GObject):
     """
     _instance = None
     blacklist = {}
+    _scale = 1
 
     def __repr__(self):
         return '<AlbumArtCache>'
 
     @log
-    def __init__(self):
+    def __init__(self, scale=1):
         GObject.GObject.__init__(self)
+        self._scale = scale
 
         self.cache_dir = os.path.join(GLib.get_user_cache_dir(), 'media-art')
         if not os.path.exists(self.cache_dir):
@@ -191,8 +204,7 @@ class AlbumArtCache(GObject.GObject):
         """Find art for the given item
 
         :param item: Grilo media item
-        :param int width: Width of the icon to return
-        :param int height: Height of the icon to return
+        :param ArtSize art_size: Size of the icon
         :param callback: Callback function when retrieved
         :param itr: Iter to return with callback
         """
@@ -231,21 +243,19 @@ class AlbumArtCache(GObject.GObject):
 
         def do_callback(pixbuf):
             if not pixbuf:
-                pixbuf = DefaultIcon().get(DefaultIcon.Type.music, art_size)
+                surface = DefaultIcon(self._scale).get(DefaultIcon.Type.music,
+                                                       art_size)
             else:
-                pixbuf = pixbuf.scale_simple(art_size.width,
-                                             art_size.height,
-                                             GdkPixbuf.InterpType.HYPER)
-                pixbuf = _make_icon_frame(pixbuf)
+                surface = _make_icon_frame(pixbuf, art_size, self._scale)
 
                 # Sets the thumbnail location for MPRIS to use.
                 item.set_thumbnail(GLib.filename_to_uri(thumb_file.get_path(),
                                                         None))
 
-            GLib.idle_add(callback, pixbuf, None, itr)
+            GLib.idle_add(callback, surface, None, itr)
             return
 
-        [success, thumb_file] = MediaArt.get_file(artist, album, "album")
+        success, thumb_file = MediaArt.get_file(artist, album, "album")
 
         if (success
                 and thumb_file.query_exists()):
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index 66719d1..99dee75 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -108,9 +108,11 @@ class Player(GObject.GObject):
         self.currentTrack = None
         self.currentTrackUri = None
         self._lastState = Gst.State.PAUSED
-        self.cache = AlbumArtCache()
-        self._no_artwork_icon = DefaultIcon().get(DefaultIcon.Type.music,
-                                                  ArtSize.xsmall)
+        scale = parent_window.get_scale_factor()
+        self.cache = AlbumArtCache(scale)
+        self._no_artwork_icon_surface = DefaultIcon(scale).get(
+            DefaultIcon.Type.music,
+            ArtSize.xsmall)
         self._missingPluginMessages = []
 
         Gst.init(None)
@@ -605,9 +607,8 @@ class Player(GObject.GObject):
         except:
             self._currentAlbum = album
 
-        self.coverImg.set_from_pixbuf(self._no_artwork_icon)
-        self.cache.lookup(
-            media, ArtSize.xsmall, self._on_cache_lookup, None)
+        self.coverImg.set_from_surface(self._no_artwork_icon_surface)
+        self.cache.lookup(media, ArtSize.xsmall, self._on_cache_lookup, None)
 
         self._currentTitle = utils.get_media_title(media)
         self.titleLabel.set_label(self._currentTitle)
@@ -662,7 +663,7 @@ class Player(GObject.GObject):
     @log
     def _on_cache_lookup(self, pixbuf, path, data=None):
         if pixbuf is not None:
-            self.coverImg.set_from_pixbuf(pixbuf)
+            self.coverImg.set_from_surface(pixbuf)
         self.emit('thumbnail-updated', path)
 
     @log
diff --git a/gnomemusic/view.py b/gnomemusic/view.py
index 742d51d..ed3b8b5 100644
--- a/gnomemusic/view.py
+++ b/gnomemusic/view.py
@@ -30,10 +30,10 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
-
 from gi.repository import Gtk
 from gi.repository import GObject
 from gi.repository import Gd
+from gi.repository import Gdk
 from gi.repository import Gio
 from gi.repository import Grl
 from gi.repository import Pango
@@ -127,9 +127,13 @@ class ViewContainer(Gtk.Stack):
         self.show_all()
         self.view.hide()
         self._items = []
-        self.cache = AlbumArtCache()
-        self._loading_icon = DefaultIcon().get(DefaultIcon.Type.loading,
-                                               ArtSize.medium)
+
+        scale = self.get_scale_factor()
+        self.cache = AlbumArtCache(scale)
+        self._loading_icon_surface = DefaultIcon(scale).get(
+            DefaultIcon.Type.loading,
+            ArtSize.medium)
+
 
         self._init = False
         grilo.connect('ready', self._on_grilo_ready)
@@ -194,7 +198,6 @@ class ViewContainer(Gtk.Stack):
 
     @log
     def _on_view_selection_changed(self, widget):
-
         if not self.selection_mode:
             return
 
@@ -241,18 +244,33 @@ class ViewContainer(Gtk.Stack):
         title = utils.get_media_title(item)
 
         _iter = self.model.append(None)
-        self.model.set(_iter,
-                       [0, 1, 2, 3, 4, 5, 7, 9],
-                       [str(item.get_id()), '', title,
-                        artist, self._loading_icon, item,
-                        0, False])
+
+        loading_icon = Gdk.pixbuf_get_from_surface(
+            self._loadin_icon_surface, 0, 0,
+            self._loading_icon_surface.get_width(),
+            self._loading_icon_surface.get_height())
+
+        self.model[_iter][0, 1, 2, 3, 4, 5, 7, 9] = [
+            str(item.get_id()),
+            '',
+            title,
+            artist,
+            loading_icon,
+            item,
+            0,
+            False
+        ]
         self.cache.lookup(item, self._iconWidth, self._iconHeight,
                           self._on_lookup_ready, _iter)
 
     @log
-    def _on_lookup_ready(self, icon, path, _iter):
-        if icon:
-            self.model.set_value(_iter, 4, icon)
+    def _on_lookup_ready(self, surface, path, _iter):
+        if surface:
+            pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0,
+                                                 surface.get_width(),
+                                                 surface.get_height())
+
+            self.model[_iter][4] = pixbuf
 
     @log
     def _add_list_renderers(self):
@@ -494,7 +512,8 @@ class Albums(ViewContainer):
 
         child.title.set_label(title)
         child.subtitle.set_label(artist)
-        child.image.set_from_pixbuf(self._loading_icon)
+
+        child.image.set_from_surface(self._loading_icon_surface)
         # In the case of off-sized icons (eg. provided in the soundfile)
         # keep the size request equal to all other icons to get proper
         # alignment with GtkFlowBox.
@@ -530,7 +549,7 @@ class Albums(ViewContainer):
                 child.check.set_active(True)
 
     def _on_lookup_ready(self, icon, path, child):
-        child.image.set_from_pixbuf(icon)
+        child.image.set_from_surface(icon)
 
     @log
     def _on_child_toggled(self, check, pspec, child):
@@ -1527,10 +1546,25 @@ class Search(ViewContainer):
         self._items = {}
         self.isStarred = None
         self.iter_to_clean = None
-        self._loading_icon = DefaultIcon().get(DefaultIcon.Type.loading,
-                                               ArtSize.small)
-        self._no_albumart_icon = DefaultIcon().get(DefaultIcon.Type.music,
-                                                   ArtSize.small)
+
+        scale = self.get_scale_factor()
+        loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading,
+                                                      ArtSize.small)
+        no_albumart_surface = DefaultIcon(scale).get(DefaultIcon.Type.music,
+                                                     ArtSize.small)
+        self._loading_icon = Gdk.pixbuf_get_from_surface(
+            loading_icon_surface,
+            0,
+            0,
+            loading_icon_surface.get_width(),
+            loading_icon_surface.get_height())
+        self._no_albumart_icon = Gdk.pixbuf_get_from_surface(
+            no_albumart_surface,
+            0,
+            0,
+            no_albumart_surface.get_width(),
+            no_albumart_surface.get_height())
+
         self._add_list_renderers()
         self.player = player
         self.head_iters = [None, None, None, None]
@@ -1704,7 +1738,9 @@ class Search(ViewContainer):
                 self.head_iters[group], -1,
                 [0, 2, 3, 4, 5, 9, 11],
                 [str(item.get_id()), title, artist,
-                 self._no_albumart_icon, item, 2 if source.get_id() != 'grl-tracker-source' else 
bool(item.get_lyrics()), category])
+                 self._no_albumart_icon, item,
+                 2 if source.get_id() != 'grl-tracker-source' \
+                    else bool(item.get_lyrics()), category])
         else:
             if not artist.casefold() in self._artists:
                 _iter = self.model.insert_with_values(
diff --git a/gnomemusic/widgets.py b/gnomemusic/widgets.py
index bd1bf6a..3787d1e 100644
--- a/gnomemusic/widgets.py
+++ b/gnomemusic/widgets.py
@@ -123,8 +123,6 @@ class AlbumWidget(Gtk.EventBox):
     """
 
     _duration = 0
-    _loading_icon = DefaultIcon().get(DefaultIcon.Type.loading, ArtSize.small)
-    _no_artwork_icon = DefaultIcon().get(DefaultIcon.Type.music, ArtSize.small)
 
     def __repr__(self):
         return '<AlbumWidget>'
@@ -137,7 +135,16 @@ class AlbumWidget(Gtk.EventBox):
         :param parent_view: The view this widget is part of
         """
         Gtk.EventBox.__init__(self)
-        self._cache = AlbumArtCache()
+
+        scale = self.get_scale_factor()
+        self._cache = AlbumArtCache(scale)
+        self._loading_icon_surface = DefaultIcon(scale).get(
+            DefaultIcon.Type.loading,
+            ArtSize.small)
+        self._no_artwork_icon_surface = DefaultIcon(scale).get(
+            DefaultIcon.Type.music,
+            ArtSize.small)
+
         self._player = player
         self._iter_to_clean = None
 
@@ -290,7 +297,8 @@ class AlbumWidget(Gtk.EventBox):
         self.selection_toolbar = selection_toolbar
         self._header_bar = header_bar
         self._album = album
-        self._ui.get_object('cover').set_from_pixbuf(self._loading_icon)
+        self._ui.get_object('cover').set_from_surface(
+            self._loading_icon_surface)
         self._cache.lookup(item, ArtSize.large, self._on_look_up, None)
         self._duration = 0
         self._create_model()
@@ -380,19 +388,16 @@ class AlbumWidget(Gtk.EventBox):
                 _("%d min") % (int(self._duration / 60) + 1))
 
     @log
-    def _on_look_up(self, pixbuf, path, data=None):
+    def _on_look_up(self, surface, path, data=None):
         """Albumart retrieved callback.
 
-        :param pixbuf: The GtkPixbuf retrieved
+        :param surface: The Cairo surface retrieved
         :param path: The filesystem location the pixbuf
         :param data: User data
         """
-        _iter = self._iter_to_clean
-        if not pixbuf:
-            pixbuf = self._no_artwork_icon
-        self._ui.get_object('cover').set_from_pixbuf(pixbuf)
-        if _iter:
-            self.model[_iter][4] = pixbuf
+        if not surface:
+            surface = self._no_artwork_icon_surface
+        self._ui.get_object('cover').set_from_surface(surface)
 
     @log
     def _update_model(self, player, playlist, current_iter):
@@ -626,16 +631,23 @@ class ArtistAlbumWidget(Gtk.Box):
         'tracks-loaded': (GObject.SignalFlags.RUN_FIRST, None, ()),
     }
 
-    _loading_icon = DefaultIcon().get(DefaultIcon.Type.loading, ArtSize.large)
-    _no_artwork_icon = DefaultIcon().get(DefaultIcon.Type.music, ArtSize.large)
-
     def __repr__(self):
         return '<ArtistAlbumWidget>'
 
     @log
     def __init__(self, artist, album, player, model, header_bar, selectionModeAllowed):
         Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
-        self._cache = AlbumArtCache()
+
+        scale = self.get_scale_factor()
+        self._cache = AlbumArtCache(scale)
+
+        self._loading_icon_surface = DefaultIcon(scale).get(
+            DefaultIcon.Type.loading,
+            ArtSize.large)
+        self._no_artwork_icon_surface = DefaultIcon(scale).get(
+            DefaultIcon.Type.music,
+            ArtSize.large)
+
         self.player = player
         self.album = album
         self.artist = artist
@@ -651,7 +663,7 @@ class ArtistAlbumWidget(Gtk.Box):
         GLib.idle_add(self._update_album_art)
 
         self.cover = self.ui.get_object('cover')
-        self.cover.set_from_pixbuf(self._loading_icon)
+        self.cover.set_from_surface(self._loading_icon_surface)
         self.songsGrid = self.ui.get_object('grid1')
         self.ui.get_object('title').set_label(album.get_title())
         if album.get_creation_date():
@@ -719,10 +731,10 @@ class ArtistAlbumWidget(Gtk.Box):
                            None)
 
     @log
-    def _get_album_cover(self, pixbuf, path, data=None):
-        if not pixbuf:
-            pixbuf = self._no_artwork_icon
-        self.cover.set_from_pixbuf(pixbuf)
+    def _get_album_cover(self, surface, path, data=None):
+        if not surface:
+            surface = self._no_artwork_icon_surface
+        self.cover.set_from_surface(surface)
 
     @log
     def track_selected(self, widget, event):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]