Blur game covers properly
This commit is contained in:
@@ -9,4 +9,9 @@
|
|||||||
color: @light_1;
|
color: @light_1;
|
||||||
background-color: @dark_5;
|
background-color: @dark_5;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is to hide a weird GdkPixbuf upscaling glitch */
|
||||||
|
#details_view_blurred_cover {
|
||||||
|
transform: scale(1.2);
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,7 @@ template CartridgesWindow : Adw.ApplicationWindow {
|
|||||||
|
|
||||||
Overlay details_view {
|
Overlay details_view {
|
||||||
name: "details_view";
|
name: "details_view";
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
[overlay]
|
[overlay]
|
||||||
Box details_view_box {
|
Box details_view_box {
|
||||||
@@ -219,6 +220,7 @@ template CartridgesWindow : Adw.ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Picture details_view_blurred_cover {
|
Picture details_view_blurred_cover {
|
||||||
|
name: "details_view_blurred_cover";
|
||||||
keep-aspect-ratio: false;
|
keep-aspect-ratio: false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,13 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from gi.repository import GdkPixbuf, Gio, GLib
|
from gi.repository import GdkPixbuf, Gio, GLib
|
||||||
|
from PIL import Image, ImageFilter, ImageStat
|
||||||
|
|
||||||
|
|
||||||
class GameCover:
|
class GameCover:
|
||||||
pixbuf = None
|
pixbuf = None
|
||||||
|
blurred = None
|
||||||
|
luminance = None
|
||||||
path = None
|
path = None
|
||||||
animation = None
|
animation = None
|
||||||
anim_iter = None
|
anim_iter = None
|
||||||
@@ -47,6 +50,8 @@ class GameCover:
|
|||||||
def new_cover(self, path=None):
|
def new_cover(self, path=None):
|
||||||
self.animation = None
|
self.animation = None
|
||||||
self.pixbuf = None
|
self.pixbuf = None
|
||||||
|
self.blurred = None
|
||||||
|
self.luminance = None
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
@@ -62,6 +67,36 @@ class GameCover:
|
|||||||
def get_pixbuf(self):
|
def get_pixbuf(self):
|
||||||
return self.animation.get_static_image() if self.animation else self.pixbuf
|
return self.animation.get_static_image() if self.animation else self.pixbuf
|
||||||
|
|
||||||
|
def get_blurred(self):
|
||||||
|
if not self.blurred:
|
||||||
|
if self.path:
|
||||||
|
with Image.open(self.path) as image:
|
||||||
|
image = (
|
||||||
|
image.convert("RGB")
|
||||||
|
.resize((8, 12))
|
||||||
|
.filter(ImageFilter.GaussianBlur(3))
|
||||||
|
)
|
||||||
|
|
||||||
|
tmp_path = Gio.File.new_tmp(None)[0].get_path()
|
||||||
|
image.save(tmp_path, "tiff", compression=None)
|
||||||
|
|
||||||
|
self.blurred = GdkPixbuf.Pixbuf.new_from_file(tmp_path)
|
||||||
|
|
||||||
|
stat = ImageStat.Stat(image.convert("L"))
|
||||||
|
|
||||||
|
self.luminance = (
|
||||||
|
(stat.mean[0] + stat.extrema[0][0]) / 510,
|
||||||
|
(stat.mean[0] + stat.extrema[0][1]) / 510,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.blurred = GdkPixbuf.Pixbuf.new_from_resource_at_scale(
|
||||||
|
"/hu/kramo/Cartridges/library_placeholder.svg", 2, 3, False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.luminance = (0.5, 0.5)
|
||||||
|
|
||||||
|
return self.blurred
|
||||||
|
|
||||||
def add_picture(self, picture):
|
def add_picture(self, picture):
|
||||||
self.pictures.add(picture)
|
self.pictures.add(picture)
|
||||||
if not self.animation:
|
if not self.animation:
|
||||||
|
|||||||
@@ -21,9 +21,8 @@ import json
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from struct import unpack_from
|
|
||||||
|
|
||||||
from gi.repository import Adw, Gdk, GdkPixbuf, Gio, GLib, Gtk
|
from gi.repository import Adw, Gdk, Gio, GLib, Gtk
|
||||||
|
|
||||||
from .game import Game
|
from .game import Game
|
||||||
|
|
||||||
@@ -71,7 +70,6 @@ class CartridgesWindow(Adw.ApplicationWindow):
|
|||||||
game_covers = {}
|
game_covers = {}
|
||||||
toasts = {}
|
toasts = {}
|
||||||
active_game = None
|
active_game = None
|
||||||
scaled_pixbuf = None
|
|
||||||
details_view_game_cover = None
|
details_view_game_cover = None
|
||||||
sort_state = "a-z"
|
sort_state = "a-z"
|
||||||
|
|
||||||
@@ -236,11 +234,9 @@ class CartridgesWindow(Adw.ApplicationWindow):
|
|||||||
self.details_view_game_cover = game.game_cover
|
self.details_view_game_cover = game.game_cover
|
||||||
self.details_view_game_cover.add_picture(self.details_view_cover)
|
self.details_view_game_cover.add_picture(self.details_view_cover)
|
||||||
|
|
||||||
self.scaled_pixbuf = (
|
self.details_view_blurred_cover.set_pixbuf(
|
||||||
self.details_view_game_cover.get_pixbuf()
|
self.details_view_game_cover.get_blurred()
|
||||||
or self.details_view_game_cover.placeholder_pixbuf
|
)
|
||||||
).scale_simple(2, 3, GdkPixbuf.InterpType.BILINEAR)
|
|
||||||
self.details_view_blurred_cover.set_pixbuf(self.scaled_pixbuf)
|
|
||||||
|
|
||||||
self.details_view_title.set_label(game.name)
|
self.details_view_title.set_label(game.name)
|
||||||
self.details_view_header_bar_title.set_title(game.name)
|
self.details_view_header_bar_title.set_title(game.name)
|
||||||
@@ -274,33 +270,12 @@ class CartridgesWindow(Adw.ApplicationWindow):
|
|||||||
self.details_view_blurred_cover.set_opacity(0.3)
|
self.details_view_blurred_cover.set_opacity(0.3)
|
||||||
return
|
return
|
||||||
|
|
||||||
colors = {
|
|
||||||
unpack_from(
|
|
||||||
"BBBB",
|
|
||||||
self.scaled_pixbuf.get_pixels(),
|
|
||||||
offset=index * self.scaled_pixbuf.get_n_channels(),
|
|
||||||
)
|
|
||||||
for index in range(6)
|
|
||||||
}
|
|
||||||
|
|
||||||
dark_theme = style_manager.get_dark()
|
dark_theme = style_manager.get_dark()
|
||||||
|
|
||||||
luminances = []
|
|
||||||
|
|
||||||
for red, green, blue, alpha in colors:
|
|
||||||
# https://en.wikipedia.org/wiki/Relative_luminance
|
|
||||||
luminance = red * 0.2126 + green * 0.7152 + blue * 0.0722
|
|
||||||
|
|
||||||
luminances.append(
|
|
||||||
(luminance * alpha) / 255**2
|
|
||||||
if dark_theme
|
|
||||||
else (alpha * (luminance - 255)) / 255**2 + 1
|
|
||||||
)
|
|
||||||
|
|
||||||
self.details_view_blurred_cover.set_opacity(
|
self.details_view_blurred_cover.set_opacity(
|
||||||
1.3 - (sum(luminances) / len(luminances) + max(luminances)) / 2
|
1.2 - self.details_view_game_cover.luminance[0]
|
||||||
if dark_theme
|
if dark_theme
|
||||||
else 0.2 + (sum(luminances) / len(luminances) + min(luminances)) / 2
|
else 0.2 + self.details_view_game_cover.luminance[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
def sort_func(self, child1, child2):
|
def sort_func(self, child1, child2):
|
||||||
|
|||||||
Reference in New Issue
Block a user