🎨 Made OnlineCoverManager more general

- Does compositing of image with a blurred background
- Stretches the original image if it's not too much
- Handles images that are too wide and images that are too tall
- Removed ItchCoverManager
This commit is contained in:
GeoffreyCoulaud
2023-06-14 00:05:38 +02:00
parent 6dd8e3965f
commit 695cc88d76
5 changed files with 89 additions and 78 deletions

View File

@@ -50,7 +50,7 @@ class ItchSourceIterator(SourceIterator):
"game_id": self.source.game_id_format.format(game_id=row[0]),
"executable": self.source.executable_format.format(cave_id=row[4]),
}
additional_data = {"itch_cover_url": row[2], "itch_still_cover_url": row[3]}
additional_data = {"online_cover_url": row[3] or row[2]}
game = Game(values, allow_side_effects=False)
yield (game, additional_data)

View File

@@ -37,12 +37,12 @@ from src.importer.sources.itch_source import ItchSource
from src.importer.sources.legendary_source import LegendarySource
from src.importer.sources.lutris_source import LutrisSource
from src.importer.sources.steam_source import SteamSource
from src.logging.setup import setup_logging, log_system_info
from src.logging.setup import log_system_info, setup_logging
from src.preferences import PreferencesWindow
from src.store.managers.display_manager import DisplayManager
from src.store.managers.file_manager import FileManager
from src.store.managers.itch_cover_manager import ItchCoverManager
from src.store.managers.local_cover_manager import LocalCoverManager
from src.store.managers.online_cover_manager import OnlineCoverManager
from src.store.managers.sgdb_manager import SGDBManager
from src.store.managers.steam_api_manager import SteamAPIManager
from src.store.store import Store
@@ -89,7 +89,7 @@ class CartridgesApplication(Adw.Application):
# Add rest of the managers for game imports
shared.store.add_manager(LocalCoverManager())
shared.store.add_manager(SteamAPIManager())
shared.store.add_manager(ItchCoverManager())
shared.store.add_manager(OnlineCoverManager())
shared.store.add_manager(SGDBManager())
shared.store.add_manager(FileManager())

View File

@@ -1,66 +0,0 @@
from pathlib import Path
import requests
from gi.repository import GdkPixbuf, Gio
from requests.exceptions import HTTPError, SSLError
from src import shared # pylint: disable=no-name-in-module
from src.game import Game
from src.store.managers.local_cover_manager import LocalCoverManager
from src.store.managers.manager import Manager
from src.utils.save_cover import resize_cover, save_cover
# TODO Remove by generalizing OnlineCoverManager
class ItchCoverManager(Manager):
"""Manager in charge of downloading the game's cover from itch.io"""
run_after = (LocalCoverManager,)
retryable_on = (HTTPError, SSLError)
def manager_logic(self, game: Game, additional_data: dict) -> None:
# Get the first matching cover url
base_cover_url: str = additional_data.get("itch_cover_url", None)
still_cover_url: str = additional_data.get("itch_still_cover_url", None)
cover_url = still_cover_url or base_cover_url
if not cover_url:
return
# Download cover
tmp_file = Gio.File.new_tmp()[0]
with requests.get(cover_url, timeout=5) as cover:
cover.raise_for_status()
Path(tmp_file.get_path()).write_bytes(cover.content)
# Create background blur
game_cover = GdkPixbuf.Pixbuf.new_from_stream_at_scale(
tmp_file.read(), 2, 2, False
).scale_simple(*shared.image_size, GdkPixbuf.InterpType.BILINEAR)
# Resize square image
itch_pixbuf = GdkPixbuf.Pixbuf.new_from_stream(tmp_file.read())
itch_pixbuf = itch_pixbuf.scale_simple(
shared.image_size[0],
itch_pixbuf.get_height() * (shared.image_size[0] / itch_pixbuf.get_width()),
GdkPixbuf.InterpType.BILINEAR,
)
# Composite
itch_pixbuf.composite(
game_cover,
0,
(shared.image_size[1] - itch_pixbuf.get_height()) / 2,
itch_pixbuf.get_width(),
itch_pixbuf.get_height(),
0,
(shared.image_size[1] - itch_pixbuf.get_height()) / 2,
1.0,
1.0,
GdkPixbuf.InterpType.BILINEAR,
255,
)
# Resize and save the cover
save_cover(game.game_id, resize_cover(pixbuf=game_cover))

View File

@@ -1,9 +1,12 @@
import logging
from pathlib import Path
import requests
from gi.repository import Gio
from gi.repository import Gio, GdkPixbuf
from requests.exceptions import HTTPError, SSLError
from PIL import Image
from src import shared
from src.game import Game
from src.store.managers.local_cover_manager import LocalCoverManager
from src.store.managers.manager import Manager
@@ -16,15 +19,89 @@ class OnlineCoverManager(Manager):
run_after = (LocalCoverManager,)
retryable_on = (HTTPError, SSLError)
def save_composited_cover(
self,
game: Game,
image_file: Gio.File,
original_width: int,
original_height: int,
target_width: int,
target_height: int,
) -> None:
"""Save the image composited with a background blur to fit the cover size"""
logging.debug(
"Compositing image for %s (%s) %dx%d -> %dx%d",
game.name,
game.game_id,
original_width,
original_height,
target_width,
target_height,
)
# Load game image
image = GdkPixbuf.Pixbuf.new_from_stream(image_file.read())
# Create background blur of the size of the cover
cover = image.scale_simple(2, 2, GdkPixbuf.InterpType.BILINEAR).scale_simple(
target_width, target_height, GdkPixbuf.InterpType.BILINEAR
)
# Center the image above the blurred background
scale = min(target_width / original_width, target_height / original_height)
left_padding = (target_width - original_width * scale) / 2
top_padding = (target_height - original_height * scale) / 2
image.composite(
cover,
# Top left of overwritten area on the destination
left_padding,
top_padding,
# Size of the overwritten area on the destination
original_width * scale,
original_height * scale,
# Offset
left_padding,
top_padding,
# Scale to apply to the resized image
scale,
scale,
# Compositing stuff
GdkPixbuf.InterpType.BILINEAR,
255,
)
# Resize and save the cover
save_cover(game.game_id, resize_cover(pixbuf=cover))
def manager_logic(self, game: Game, additional_data: dict) -> None:
# Ensure that we have a cover to download
cover_url = additional_data.get("online_cover_url", None)
cover_url = additional_data.get("online_cover_url")
if not cover_url:
return
# Download cover
tmp_file = Gio.File.new_tmp()[0]
image_file = Gio.File.new_tmp()[0]
image_path = Path(image_file.get_path())
with requests.get(cover_url, timeout=5) as cover:
cover.raise_for_status()
Path(tmp_file.get_path()).write_bytes(cover.content)
# Resize and save
save_cover(game.game_id, resize_cover(tmp_file.get_path()))
image_path.write_bytes(cover.content)
# Get image size
cover_width, cover_height = shared.image_size
with Image.open(image_path) as pil_image:
width, height = pil_image.size
# Composite the image if its aspect ratio differs too much
# (allow the side that is smaller to be stretched by a small percentage)
max_diff_proportion = 0.12
scale = min(cover_width / width, cover_height / height)
width_diff = (cover_width - (width * scale)) / cover_width
height_diff = (cover_height - (height * scale)) / cover_height
diff = width_diff + height_diff
if diff < max_diff_proportion:
save_cover(game.game_id, resize_cover(image_path))
else:
self.save_composited_cover(
game, image_file, width, height, cover_width, cover_height
)

View File

@@ -4,7 +4,7 @@ from requests.exceptions import HTTPError, SSLError
from src.game import Game
from src.store.managers.async_manager import AsyncManager
from src.store.managers.itch_cover_manager import ItchCoverManager
from src.store.managers.online_cover_manager import OnlineCoverManager
from src.store.managers.local_cover_manager import LocalCoverManager
from src.store.managers.steam_api_manager import SteamAPIManager
from src.utils.steamgriddb import SGDBAuthError, SGDBHelper
@@ -13,7 +13,7 @@ from src.utils.steamgriddb import SGDBAuthError, SGDBHelper
class SGDBManager(AsyncManager):
"""Manager in charge of downloading a game's cover from steamgriddb"""
run_after = (SteamAPIManager, LocalCoverManager, ItchCoverManager)
run_after = (SteamAPIManager, LocalCoverManager, OnlineCoverManager)
retryable_on = (HTTPError, SSLError, ConnectionError, JSONDecodeError)
def manager_logic(self, game: Game, _additional_data: dict) -> None: