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



commit 94d67a1e8a2e42c4835745dfd7f34480d36a0086
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 limits.

 gnomemusic/albumartcache.py |   48 +++++++++++++++----------
 gnomemusic/player.py        |   11 +++---
 gnomemusic/view.py          |   81 +++++++++++++++++++++++++++++++------------
 gnomemusic/widgets.py       |   31 ++++++++++------
 4 files changed, 114 insertions(+), 57 deletions(-)
---
diff --git a/gnomemusic/albumartcache.py b/gnomemusic/albumartcache.py
index 385f86f..8f75262 100644
--- a/gnomemusic/albumartcache.py
+++ b/gnomemusic/albumartcache.py
@@ -47,13 +47,13 @@ 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()
+    w = art_size.width * scale
+    h = art_size.height * scale
 
     new_pixbuf = pixbuf.scale_simple(w - border * 2,
                                      h - border * 2,
@@ -83,7 +83,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 +112,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 +148,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 +180,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 +203,8 @@ 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 int scale: Scale factor (hidpi)
         :param callback: Callback function when retrieved
         :param itr: Iter to return with callback
         """
@@ -229,12 +241,10 @@ class AlbumArtCache(GObject.GObject):
 
         def do_callback(pixbuf):
             if not pixbuf:
-                pixbuf = DefaultIcon().get(DefaultIcon.Type.music, art_size)
+                pixbuf = 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)
+                pixbuf = _make_icon_frame(pixbuf, art_size, self.scale)
 
             GLib.idle_add(callback, pixbuf, None, itr)
             return
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index 66719d1..cbbb76f 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -108,9 +108,10 @@ 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 = DefaultIcon(scale).get(DefaultIcon.Type.music,
+                                                       ArtSize.xsmall)
         self._missingPluginMessages = []
 
         Gst.init(None)
@@ -605,7 +606,7 @@ class Player(GObject.GObject):
         except:
             self._currentAlbum = album
 
-        self.coverImg.set_from_pixbuf(self._no_artwork_icon)
+        self.coverImg.set_from_surface(self._no_artwork_icon)
         self.cache.lookup(
             media, ArtSize.xsmall, self._on_cache_lookup, None)
 
@@ -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..91e87d4 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,13 +512,15 @@ 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.
         child.image.set_property("width-request", ArtSize.medium.width)
         child.image.set_property("height-request", ArtSize.medium.height)
 
+
         child.events.connect('button-release-event',
                              self._on_album_event_triggered,
                              child)
@@ -530,7 +550,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 +1547,14 @@ 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()
+        self._loading_icon_surface = DefaultIcon(scale).get(
+            DefaultIcon.Type.loading,
+            ArtSize.small)
+        self._no_albumart_surface = DefaultIcon(scale).get(
+            DefaultIcon.Type.music,
+            ArtSize.small)
         self._add_list_renderers()
         self.player = player
         self.head_iters = [None, None, None, None]
@@ -1691,27 +1715,40 @@ class Search(ViewContainer):
         except:
             pass
 
+        loading_icon = Gdk.pixbuf_get_from_surface(
+            self._loading_icon_surface,
+            0, 0,
+            self._loading_icon_surface.get_width(),
+            self._loading_icon_surface.get_height())
+        no_albumart_icon = Gdk.pixbuf_get_from_surface(
+            self._no_albumart_surface,
+            0, 0,
+            self._no_albumart_surface.get_width(),
+            self._no_albumart_surface.get_height())
+
         _iter = None
         if category == 'album':
             _iter = self.model.insert_with_values(
                 self.head_iters[group], -1,
                 [0, 2, 3, 4, 5, 9, 11],
                 [str(item.get_id()), title, artist,
-                 self._loading_icon, item, 2, category])
+                 loading_icon, item, 2, category])
             self.cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter)
         elif category == 'song':
             _iter = self.model.insert_with_values(
                 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])
+                 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(
                     self.head_iters[group], -1,
                     [0, 2, 4, 5, 9, 11],
                     [str(item.get_id()), artist,
-                     self._loading_icon, item, 2, category])
+                     loading_icon, item, 2, category])
                 self.cache.lookup(item, ArtSize.small, self._on_lookup_ready,
                                   _iter)
                 self._artists[artist.casefold()] = {'iter': _iter, 'albums': []}
diff --git a/gnomemusic/widgets.py b/gnomemusic/widgets.py
index bd1bf6a..124c391 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,14 @@ 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 = DefaultIcon(scale).get(DefaultIcon.Type.loading,
+                                                    ArtSize.small)
+        self._no_artwork_icon = DefaultIcon(scale).get(DefaultIcon.Type.music,
+                                                       ArtSize.small)
+
         self._player = player
         self._iter_to_clean = None
 
@@ -290,7 +295,7 @@ 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)
         self._cache.lookup(item, ArtSize.large, self._on_look_up, None)
         self._duration = 0
         self._create_model()
@@ -390,7 +395,7 @@ class AlbumWidget(Gtk.EventBox):
         _iter = self._iter_to_clean
         if not pixbuf:
             pixbuf = self._no_artwork_icon
-        self._ui.get_object('cover').set_from_pixbuf(pixbuf)
+        self._ui.get_object('cover').set_from_surface(pixbuf)
         if _iter:
             self.model[_iter][4] = pixbuf
 
@@ -626,16 +631,20 @@ 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 = DefaultIcon(scale).get(DefaultIcon.Type.loading,
+                                                    ArtSize.large)
+        self._no_artwork_icon = DefaultIcon(scale).get(DefaultIcon.Type.music,
+                                                       ArtSize.large)
+
         self.player = player
         self.album = album
         self.artist = artist
@@ -651,7 +660,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)
         self.songsGrid = self.ui.get_object('grid1')
         self.ui.get_object('title').set_label(album.get_title())
         if album.get_creation_date():
@@ -722,7 +731,7 @@ class ArtistAlbumWidget(Gtk.Box):
     def _get_album_cover(self, pixbuf, path, data=None):
         if not pixbuf:
             pixbuf = self._no_artwork_icon
-        self.cover.set_from_pixbuf(pixbuf)
+        self.cover.set_from_surface(pixbuf)
 
     @log
     def track_selected(self, widget, event):


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