1
0
Fork 0
mirror of synced 2024-06-26 18:20:50 +12:00

ImageManager: Generate shortcut icons when fetching cover images.

This commit is contained in:
loathingKernel 2023-02-08 15:08:55 +02:00
parent 97adb6e1c2
commit b36dfdd2c6
2 changed files with 73 additions and 32 deletions

View file

@ -6,7 +6,7 @@ from enum import Enum
# from concurrent import futures
from logging import getLogger
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional
from typing import Tuple, Dict, Union, Type, List, Callable
import requests
@ -16,19 +16,19 @@ from PyQt5.QtCore import (
QObject,
QSize,
QThreadPool,
QRunnable,
QRunnable, QRect, QRectF,
)
from PyQt5.QtGui import (
QPixmap,
QImage,
QPainter,
QPainter, QPainterPath, QColor, QBrush, QTransform, QPen,
)
from PyQt5.QtWidgets import QApplication
from legendary.models.game import Game
from rare.lgndr.core import LegendaryCore
from rare.models.signals import GlobalSignals
from rare.utils.paths import image_dir, resources_path
from rare.utils.paths import image_dir, resources_path, desktop_icon_suffix
# from requests_futures.sessions import FuturesSession
@ -119,6 +119,7 @@ class ImageManager(QObject):
def __init__(self, signals: GlobalSignals, core: LegendaryCore):
# lk: the ordering in __img_types matters for the order of fallbacks
# self.__img_types: Tuple = ("DieselGameBoxTall", "Thumbnail", "DieselGameBoxLogo", "DieselGameBox")
self.__img_types: Tuple = ("DieselGameBoxTall", "Thumbnail", "DieselGameBoxLogo")
self.__dl_retries = 1
self.__worker_app_names: List[str] = []
@ -151,6 +152,9 @@ class ImageManager(QObject):
def __img_gray(self, app_name: str) -> Path:
return self.__img_dir(app_name).joinpath("uninstalled.png")
def __img_desktop_icon(self, app_name: str) -> Path:
return self.__img_dir(app_name).joinpath(f"icon.{desktop_icon_suffix()}")
def __prepare_download(self, game: Game, force: bool = False) -> Tuple[List, Dict]:
if force and self.__img_dir(game.app_name).exists():
self.__img_color(game.app_name).unlink(missing_ok=True)
@ -182,13 +186,17 @@ class ImageManager(QObject):
# lk: Find updates or initialize if images are missing.
# lk: `updates` will be empty for games without images
# lk: so everything below it is skipped
if not self.__img_color(game.app_name).is_file() or not self.__img_gray(game.app_name).is_file():
if not (
self.__img_color(game.app_name).is_file()
and self.__img_gray(game.app_name).is_file()
and self.__img_desktop_icon(game.app_name).is_file()
):
updates = [image for image in game.metadata["keyImages"] if image["type"] in self.__img_types]
else:
updates = list()
for image in game.metadata["keyImages"]:
if image["type"] in self.__img_types:
if json_data[image["type"]] != image["md5"]:
if image["type"] not in json_data.keys() or json_data[image["type"]] != image["md5"]:
updates.append(image)
return updates, json_data
@ -204,11 +212,11 @@ class ImageManager(QObject):
updates = [
image
for image in updates
if cache_data[image["type"]] is None or json_data[image["type"]] != image["md5"]
if cache_data.get(image["type"], None) is None or json_data[image["type"]] != image["md5"]
]
# Download
# # lk: Keep this so I don't have to go looking for it again,
# # lk: Keep this here, so I don't have to go looking for it again,
# # lk: it might be useful in the future.
# if use_async and len(updates) > 1:
# session = FuturesSession(max_workers=len(self.__img_types))
@ -252,6 +260,47 @@ class ImageManager(QObject):
return bool(updates)
__icon_overlay: Optional[QPainterPath] = None
@staticmethod
def __generate_icon_overlay(rect: QRect) -> QPainterPath:
if ImageManager.__icon_overlay is not None:
return ImageManager.__icon_overlay
rounded_path = QPainterPath()
rounded_path.addRoundedRect(
QRectF(
rect.width() * 0.05,
rect.height() * 0.05,
rect.width() - (rect.width() * 0.1),
rect.height() - (rect.width() * 0.1)
),
rect.height() * 0.2,
rect.height() * 0.2,
)
ImageManager.__icon_overlay = rounded_path
return ImageManager.__icon_overlay
@staticmethod
def __convert_icon(cover: QImage) -> QImage:
icon_size = QSize(128, 128)
icon = QImage(icon_size, QImage.Format_ARGB32_Premultiplied)
painter = QPainter(icon)
painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setCompositionMode(QPainter.CompositionMode_Source)
painter.fillRect(icon.rect(), Qt.transparent)
overlay = ImageManager.__generate_icon_overlay(icon.rect())
brush = QBrush(cover)
scale = max(icon.width()/cover.width(), icon.height()/cover.height())
transform = QTransform().scale(scale, scale)
brush.setTransform(transform)
painter.fillPath(overlay, brush)
pen = QPen(Qt.black, 2)
painter.setPen(pen)
painter.drawPath(overlay)
painter.end()
return icon
def __convert(self, game, images, force=False) -> None:
for image in [self.__img_color(game.app_name), self.__img_gray(game.app_name)]:
if force and image.exists():
@ -283,6 +332,8 @@ class ImageManager(QObject):
painter.end()
cover = cover.scaled(ImageSize.Image.size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
icon = self.__convert_icon(cover)
icon.save(str(self.__img_desktop_icon(game.app_name)), format=desktop_icon_suffix().upper())
# this is not required if we ever want to re-apply the alpha channel
# cover = cover.convertToFormat(QImage.Format_Indexed8)
@ -290,18 +341,12 @@ class ImageManager(QObject):
# add the alpha channel back to the cover
cover = cover.convertToFormat(QImage.Format_ARGB32_Premultiplied)
cover.save(
str(self.__img_color(game.app_name)),
format="PNG",
)
cover.save(str(self.__img_color(game.app_name)), format="PNG")
# quick way to convert to grayscale
cover = cover.convertToFormat(QImage.Format_Grayscale8)
# add the alpha channel back to the grayscale cover
cover = cover.convertToFormat(QImage.Format_ARGB32_Premultiplied)
cover.save(
str(self.__img_gray(game.app_name)),
format="PNG",
)
cover.save(str(self.__img_gray(game.app_name)), format="PNG")
def __compress(self, game: Game, data: Dict) -> None:
archive = open(self.__img_cache(game.app_name), "wb")

View file

@ -74,27 +74,32 @@ def desktop_dir() -> Path:
def applications_dir() -> Path:
return Path(QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation))
# fmt: off
__link_suffix = {
"Windows": ".lnk",
"Linux": ".desktop",
}
def desktop_links_supported() -> bool:
return platform.system() in __link_suffix.keys()
__icon_suffix = {
"Windows": "ico",
"Linux": "png",
}
def desktop_icon_suffix() -> str:
return __icon_suffix[platform.system()]
__link_type = {
"desktop": desktop_dir(),
# lk: for some undocumented reason, on Windows we used the parent directory
# lk: for start menu items. Mirror it here for backwards compatibility
"start_menu": applications_dir().parent if platform.system() == "Windows" else applications_dir(),
}
def desktop_link_types() -> List:
return list(__link_type.keys())
# fmt: on
def desktop_link_path(link_name: str, link_type: str) -> Path:
@ -170,20 +175,11 @@ def create_desktop_link(app_name: str, app_title: str = "", link_name: str = "",
for_rare = app_name == "rare_shortcut"
if for_rare:
icon_path = resources_path.joinpath("images", "Rare.png")
if platform.system() == "Windows":
icon_path = resources_path.joinpath("images", "Rare.ico")
icon_path = resources_path.joinpath("images", f"Rare.{desktop_icon_suffix()}")
app_title = "Rare"
link_name = "Rare"
else:
icon_path = image_dir().joinpath(app_name, "installed.png")
if platform.system() == "Windows":
icon_path = image_dir().joinpath(app_name, "installed.ico")
if not icon_path.exists():
img = QImage()
img.load(str(icon_path.with_suffix(".png")))
img.save(str(icon_path))
logger.info("Created ico file")
icon_path = image_dir().joinpath(app_name, f"icon.{desktop_icon_suffix()}")
if not app_title or not link_name:
logger.error("Missing app_title or link_name")
return False