"""Import the user's Arkham City artwork and build the app icon from it. python tools/make_assets.py ["C:/Users//Downloads/Arkham City"] tools/build_app.py runs this by itself when app/assets is missing or older than this script (from the default folder or --art FOLDER). Input folder: bac_logo_full.png (logo with transparent background) and wallpaper.jpg. Output (app/assets/, git-ignored because it is Warner Bros. artwork): wallpaper.jpg background of the filter panel app_icon.png 256 px icon: the logo bat in black over a red audio waveform, on a white rounded tile app_icon.ico Windows icon (16-256 px) """ import math import os import shutil import struct import sys from PySide6.QtCore import QBuffer, QByteArray, QIODevice, QPointF, QRect, QRectF, Qt from PySide6.QtGui import QColor, QGuiApplication, QImage, QLinearGradient, QPainter ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) OUT = os.path.join(ROOT, "app", "assets") DEFAULT_SRC = os.path.join(os.path.expanduser("~"), "Downloads", "Arkham City") RED, RED_HI = "#b0242c", "#d8434b" # the app accent def crop_bat(logo): """The bat is the opaque block above the first fully transparent band (the text sits below it).""" logo = logo.convertToFormat(QImage.Format.Format_ARGB32) w, h = logo.width(), logo.height() opaque = [any(logo.pixelColor(x, y).alpha() > 40 for x in range(0, w, 2)) for y in range(h)] top = opaque.index(True) bottom = top while bottom < h and (opaque[bottom] or any(opaque[bottom:bottom + 12])): bottom += 1 cols = [x for x in range(w) if any(logo.pixelColor(x, y).alpha() > 40 for y in range(top, bottom, 2))] return logo.copy(QRect(cols[0], top, cols[-1] - cols[0] + 1, bottom - top)) def silhouette(bat, w, color): """The bat's outline filled with one flat colour (drops the stone texture and the lettering).""" h = round(w * bat.height() / bat.width()) img = bat.scaled(w, h, Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) p = QPainter(img) p.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn) p.fillRect(img.rect(), QColor(color)) p.end() return img def draw_icon(bat, s): """White rounded tile, a red audio waveform (rounded bars) across it, the logo bat in black on top.""" img = QImage(s, s, QImage.Format.Format_ARGB32) img.fill(Qt.GlobalColor.transparent) p = QPainter(img) p.setRenderHint(QPainter.RenderHint.Antialiasing) p.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) small = s <= 24 m = s * 0.04 bg = QLinearGradient(0, 0, 0, s) bg.setColorAt(0, QColor("#ffffff")) bg.setColorAt(1, QColor("#ececec")) p.setPen(Qt.PenStyle.NoPen) p.setBrush(bg) p.drawRoundedRect(QRectF(m, m, s - 2 * m, s - 2 * m), s * 0.22, s * 0.22) # waveform: symmetric bars, tallest in the middle so they rise above and below the bat cy = s / 2 n = 5 if small else 9 x0, x1 = s * 0.12, s * 0.88 step = (x1 - x0) / n bar = step * 0.68 for i in range(n): j = min(i, n - 1 - i) # mirror t = (i + 0.5) / n env = math.exp(-((t - 0.5) / 0.34) ** 2) h = s * (0.10 + 0.32 * env * (0.65 + 0.35 * abs(math.sin(j * 2.3 + 0.7)))) g = QLinearGradient(0, cy - h, 0, cy + h) g.setColorAt(0, QColor(RED_HI)) g.setColorAt(0.5, QColor(RED)) g.setColorAt(1, QColor(RED_HI)) p.setBrush(g) p.drawRoundedRect(QRectF(x0 + i * step + (step - bar) / 2, cy - h, bar, 2 * h), bar / 2, bar / 2) # the logo bat, flat black sil = silhouette(bat, max(1, round(s * (0.84 if small else 0.78))), "#0d0d0d") p.drawImage(QPointF((s - sil.width()) / 2, cy - sil.height() / 2), sil) p.end() return img def write_ico(images, path): """Multi-size .ico with PNG-compressed entries (Windows Vista+).""" blobs = [] for im in images: ba = QByteArray() buf = QBuffer(ba) buf.open(QIODevice.OpenModeFlag.WriteOnly) im.save(buf, "PNG") blobs.append((im.width(), bytes(ba))) out = struct.pack(" 1 else DEFAULT_SRC) global _APP _APP = QGuiApplication.instance() or QGuiApplication(sys.argv[:1]) # needed for QPainter os.makedirs(OUT, exist_ok=True) logo = QImage(os.path.join(src, "bac_logo_full.png")) if logo.isNull(): raise SystemExit("bac_logo_full.png not found in %s" % src) bat = crop_bat(logo) shutil.copy(os.path.join(src, "wallpaper.jpg"), os.path.join(OUT, "wallpaper.jpg")) icon = draw_icon(bat, 256) icon.save(os.path.join(OUT, "app_icon.png")) write_ico([draw_icon(bat, n) for n in (16, 24, 32, 48, 64, 128, 256)], os.path.join(OUT, "app_icon.ico")) print("assets written to", OUT) if __name__ == "__main__": main()