DEV Community
Generating a contact sheet for an osu! skin with Python and Pillow
An osu! skin is a folder with anywhere from 50 to 500 PNG files. Opening them one by one to check consistency is slow, and file browsers show transparent images on white, which hides half of a typical skin. A contact sheet, one image with every sprite tiled on a checkerboard and labelled, makes it much easier to review a skin, spot missing @2x files and share a preview. Here is a small, dependency-light script that does exactly that. The script #!/usr/bin/env python3
"""contact_sheet.py SKIN_DIR OUT.png - tile every PNG in an osu! skin folder."""
import sys
from pathlib import Path
from PIL import Image, ImageDraw
CELL, LABEL, COLS = 160, 18, 8
def checker(w, h, s=10):
img = Image.new("RGB", (w, h), (200, 200, 200))
d = ImageDraw.Draw(img)
for y in range(0, h, s):
for x in range(0, w, s):
if (x // s + y // s) % 2:
d.rectangle([x, y, x + s - 1, y + s - 1], fill=(160, 160, 160))
return img
def main(skin, out):
files = sorted(p for p in Path(skin).glob("*.png") if "@2x" not in p.stem)
rows = (len(files) + COLS - 1) // COLS
sheet = Image.new("RGB", (COLS * CELL, rows * (CELL + LABEL)), (30, 30, 30))
draw = ImageDraw.Draw(sheet)
problems = []
for i, p in enumerate(files):
hd = p.with_name(p.stem + "@2x.png")
src = hd if hd.exists() else p
im = Image.open(src).convert("RGBA")
if hd.exists():
sd = Image.open(p)
if (sd.width * 2, sd.height * 2) != im.size:
problems.append(f"{p.name}: @2x is {im.size}, SD is {sd.size}")
else:
problems.append(f"{p.name}: no @2x version")
im.thumbnail((CELL - 8, CELL - 8))
x, y = (i % COLS) * CELL, (i // COLS) * (CELL + LABEL)
cell = checker(CELL, CELL)
cell.paste(im, ((CELL - im.width) // 2, (CELL - im.height) // 2), im)
sheet.paste(cell, (x, y))
draw.text((x + 4, y + CELL + 2), p.stem[:22], fill=(240, 240, 240))
sheet.save(out)
print("\n".join(problems) or "no size problems found")
if name == "main":
main(sys.argv[1], sys.argv[2])
Install Pillow with pip install pillow, then run: python3 contact_sheet.py "$HOME/osu!/Skins/My Skin" sheet.png
What it gives you A checkerboard background. Transparent and semi-transparent pixels are visible, so faint glows and accidental halos stand out. One cell per element. The @2x image is preferred when it exists, so you review the version most players with 1080p or higher screens actually see. A size report. Each @2x file should be exactly twice the size of its SD file. The script lists every mismatch and every element with no @2x version. Ideas for extending it Group cells by prefix (hit, slider, spinner, score-, mania) so related elements sit together. Read skin.ini and draw the combo colours as swatches at the top of the sheet. Skip animation frames after -0 to keep the sheet short, or put each animation on its own row. Flag files above a size threshold (say 1 MB) as candidates for compression. Why bother A contact sheet is the fastest way to answer "does this skin look like one consistent design?" It also makes a good first image for a skin release post, because it shows everything at once rather than one gameplay screenshot. If you want to see how finished skins hold together, the osuskins.org skin library has gameplay screenshots for a large collection of skins, and the osuskins.org skin making notes list the element names this script will be tiling.
Powered by Readybot.io
External Link
Platform
You are about to visit:
https://example.comThis will open in a new tab. Make sure you trust this link.