Skip to content

Commit 3b0c5a0

Browse files
authored
Add brilliant-diamond-shining-pearl and scarlet-violet sprites (#188)
* Add brilliant-diamond-shining-pearl and scarlet-violet sprites * Fix wrong file names * Fix some more sprite names * Fix typos in file names of sprites * Add padding to brilliant-diamond-shining-pearl sprites. * Swap gender sprite of 678 * Update README.md and add scripts for generating report and add padding to sprites. * Update generate_report.py
1 parent cd50781 commit 3b0c5a0

File tree

1,500 files changed

+363
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,500 files changed

+363
-0
lines changed

README.md

Lines changed: 19 additions & 0 deletions

scripts/generate_report.py

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# python .\generate_report.py --local-path "../sprites/pokemon/versions" --output .\out\report.html
2+
3+
import os
4+
import argparse
5+
import logging
6+
from datetime import datetime
7+
from typing import Dict, Set, Tuple, List
8+
9+
import requests
10+
import requests_cache
11+
import natsort
12+
13+
14+
# ---------------------------------------------------------------------------
15+
# Setup caching (24h)
16+
# ---------------------------------------------------------------------------
17+
requests_cache.install_cache(
18+
"api_cache",
19+
backend="sqlite",
20+
use_cache_dir=True,
21+
expire_after=86400
22+
)
23+
24+
25+
# ---------------------------------------------------------------------------
26+
# Static folder config, extend this map to test more generations/games/folders
27+
# ---------------------------------------------------------------------------
28+
FOLDER: Dict[str, List[str]] = {
29+
"generation-viii": [
30+
"brilliant-diamond-shining-pearl"
31+
],
32+
"generation-ix": [
33+
"scarlet-violet"
34+
]
35+
}
36+
37+
38+
# Typing
39+
FileInfo = Dict[str, Tuple[str, str, Set[str]]]
40+
41+
42+
# ---------------------------------------------------------------------------
43+
# Logging configuration
44+
# ---------------------------------------------------------------------------
45+
logging.basicConfig(
46+
level=logging.INFO,
47+
format="[%(levelname)s] %(message)s"
48+
)
49+
logger = logging.getLogger(__name__)
50+
51+
52+
# ---------------------------------------------------------------------------
53+
# API helper functions
54+
# ---------------------------------------------------------------------------
55+
def get_pokemon_data(api_url: str, endpoint: str, identifier: str) -> dict:
56+
"""Request PokéAPI data for a specific endpoint."""
57+
url = f"{api_url}/{endpoint}/{identifier}"
58+
response = requests.get(url)
59+
60+
if response.status_code != 200:
61+
logger.warning(f"API request failed: {url} (Status {response.status_code})")
62+
return {}
63+
64+
return response.json()
65+
66+
67+
# ---------------------------------------------------------------------------
68+
# Local filesystem scan
69+
# ---------------------------------------------------------------------------
70+
def get_local_images(
71+
local_path: str,
72+
folder_config: Dict[str, List[str]]
73+
) -> FileInfo:
74+
"""Scan local directories for image files."""
75+
all_files_by_path: FileInfo = {}
76+
77+
for gen_key, games in folder_config.items():
78+
for game_key in games:
79+
path = os.path.join(local_path, gen_key, game_key)
80+
81+
try:
82+
files = {
83+
f for f in os.listdir(path)
84+
if os.path.isfile(os.path.join(path, f))
85+
}
86+
87+
logger.info(f"Found {len(files)} files in '{path}'.")
88+
all_files_by_path[path] = (gen_key, game_key, files)
89+
90+
except FileNotFoundError:
91+
logger.warning(f"Local path '{path}' not found. Skipping.")
92+
all_files_by_path[path] = (gen_key, game_key, set())
93+
94+
return all_files_by_path
95+
96+
97+
# ---------------------------------------------------------------------------
98+
# HTML generation
99+
# ---------------------------------------------------------------------------
100+
def create_table(
101+
path_prefix: str,
102+
generation_key: str,
103+
game_key: str,
104+
file_set: Set[str],
105+
api_url: str
106+
) -> str:
107+
"""Generate the HTML table displaying comparison images."""
108+
table_rows = ""
109+
110+
for filename in natsort.natsorted(file_set):
111+
identifier = os.path.splitext(filename)[0]
112+
base_id = identifier.split("-", 1)[0]
113+
114+
is_variant = "-" in identifier
115+
variant_suffix = identifier.split("-", 1)[1] if is_variant else ""
116+
117+
logger.info(f"Processing '{filename}'")
118+
119+
final_api_identifier = base_id
120+
error = False
121+
found_variant_match = False
122+
123+
# Variant handling
124+
if is_variant:
125+
species_data = get_pokemon_data(api_url, "pokemon-species", base_id)
126+
127+
if species_data:
128+
for variety in species_data.get("varieties", []):
129+
api_name = variety.get("pokemon", {}).get("name", "")
130+
131+
if api_name.endswith(f"-{variant_suffix}"):
132+
final_api_identifier = api_name
133+
found_variant_match = True
134+
error = False
135+
break
136+
else:
137+
error = True
138+
else:
139+
error = True
140+
141+
# Base Pokémon fetch
142+
pokemon = get_pokemon_data(api_url, "pokemon", final_api_identifier)
143+
pokemon_sprites = pokemon.get("sprites") if pokemon else None
144+
145+
# Form fallback
146+
if is_variant and not found_variant_match and pokemon:
147+
for form in pokemon.get("forms", []):
148+
form_name = form.get("name", "")
149+
if form_name.endswith(f"-{variant_suffix}"):
150+
form_data = get_pokemon_data(api_url, "pokemon-form", form_name)
151+
pokemon_sprites = form_data.get("sprites", {})
152+
error = False
153+
break
154+
155+
# Extract sprites
156+
default_sprite = (
157+
pokemon_sprites.get("front_default", "")
158+
if pokemon_sprites else ""
159+
)
160+
161+
version_sprite = (
162+
pokemon_sprites.get("versions", {})
163+
.get(generation_key, {})
164+
.get(game_key, {})
165+
.get("front_default", "")
166+
if pokemon_sprites else ""
167+
)
168+
169+
# Add HTML row
170+
table_rows += f"""
171+
<div class="inline-flex flex-col items-center m-1 p-2 border-2 {'bg-red-600' if error else 'border-indigo-600'}">
172+
<span class="text-xs text-gray-500">{identifier}</span>
173+
<img src="{default_sprite}" style="max-width: 96px; object-fit: contain;">
174+
<img src="{version_sprite}" style="max-width: 96px; object-fit: contain;">
175+
</div>
176+
"""
177+
178+
return f"""
179+
<h2 class="text-xl font-bold mt-6 mb-3 border-b-2 pb-1">
180+
Analysis for path: <code>{path_prefix}</code> ({len(file_set)} files)
181+
</h2>
182+
<div class="flex flex-wrap">
183+
{table_rows}
184+
</div>
185+
"""
186+
187+
188+
# ---------------------------------------------------------------------------
189+
# Full report generation
190+
# ---------------------------------------------------------------------------
191+
def generate_report(
192+
local_path: str,
193+
api_url: str,
194+
output_file: str,
195+
cli_args: dict
196+
) -> None:
197+
"""Generate the entire HTML report."""
198+
all_files_by_path = get_local_images(local_path, FOLDER)
199+
200+
tables_html = ""
201+
for path, (gen_key, game_key, files) in all_files_by_path.items():
202+
if files:
203+
tables_html += create_table(
204+
path,
205+
gen_key,
206+
game_key,
207+
files,
208+
api_url
209+
)
210+
211+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
212+
213+
# Render CLI args as HTML
214+
cli_args_html = "<ul>" + "".join(
215+
f"<li><strong>{k}</strong>: {v}</li>" for k, v in cli_args.items()
216+
) + "</ul>"
217+
218+
html_content = f"""
219+
<!DOCTYPE html>
220+
<html>
221+
<head>
222+
<title>Sprites Report</title>
223+
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
224+
</head>
225+
<body class="p-6 bg-gray-100">
226+
<h1 class="text-3xl font-bold mb-4">Sprites Report</h1>
227+
<p><strong>Generated on:</strong> {timestamp}</p>
228+
<h2 class="text-xl font-bold mt-2 mb-2">CLI Arguments:</h2>
229+
{cli_args_html}
230+
231+
{tables_html}
232+
</body>
233+
</html>
234+
"""
235+
236+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
237+
238+
with open(output_file, "w", encoding="utf-8") as f:
239+
f.write(html_content)
240+
241+
logger.info(f"Report successfully created: {os.path.abspath(output_file)}")
242+
243+
244+
# ---------------------------------------------------------------------------
245+
# CLI handling
246+
# ---------------------------------------------------------------------------
247+
def parse_args():
248+
parser = argparse.ArgumentParser(
249+
description="Generate Pokémon sprite comparison report."
250+
)
251+
252+
parser.add_argument(
253+
"--local-path",
254+
required=True,
255+
help="Base directory containing the local sprite folders."
256+
)
257+
258+
parser.add_argument(
259+
"--api-url",
260+
help="Base URL of the Pokémon API.",
261+
default="http://localhost/api/v2"
262+
)
263+
264+
parser.add_argument(
265+
"--output",
266+
required=True,
267+
help="Path to the generated HTML report."
268+
)
269+
270+
parser.add_argument(
271+
"--clear-cache",
272+
action="store_true",
273+
help="Clear requests_cache before execution.",
274+
default=False
275+
)
276+
277+
return parser.parse_args()
278+
279+
280+
if __name__ == "__main__":
281+
args = parse_args()
282+
283+
if args.clear_cache:
284+
requests_cache.clear()
285+
logger.info("Cache cleared before run.")
286+
287+
generate_report(
288+
local_path=args.local_path,
289+
api_url=args.api_url,
290+
output_file=args.output,
291+
cli_args=vars(args)
292+
)

scripts/pad_to_canvas.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# python .\pad_to_canvas.py --input ../sprites/pokemon/versions/generation-viii/brilliant-diamond-shining-pearl --output ../sprites/pokemon/versions/generation-viii/brilliant-diamond-shining-pearl_padded
2+
3+
import os
4+
import argparse
5+
from PIL import Image
6+
7+
def pad_images(input_folder, output_folder, canvas_size):
8+
os.makedirs(output_folder, exist_ok=True)
9+
10+
for filename in os.listdir(input_folder):
11+
if not filename.lower().endswith(".png"):
12+
continue
13+
14+
img_path = os.path.join(input_folder, filename)
15+
img = Image.open(img_path).convert("RGBA") # keep transparency
16+
17+
# skip images that are taller than the canvas
18+
if img.height > canvas_size:
19+
print(f"Skipping {filename} (height {img.height}px > {canvas_size}px)")
20+
continue
21+
22+
# create transparent canvas
23+
canvas = Image.new("RGBA", (canvas_size, canvas_size), (0, 0, 0, 0))
24+
25+
# bottom-center placement
26+
x = (canvas_size - img.width) // 2
27+
y = canvas_size - img.height
28+
29+
canvas.paste(img, (x, y), img)
30+
31+
out_path = os.path.join(output_folder, filename)
32+
canvas.save(out_path)
33+
34+
print(f"Saved: {out_path}")
35+
36+
print("Done!")
37+
38+
# ---------------------------------------------------------------------------
39+
# CLI handling
40+
# ---------------------------------------------------------------------------
41+
def parse_args():
42+
parser = argparse.ArgumentParser(description="Pad images to a transparent square canvas.")
43+
parser.add_argument("--input", required=True, help="Input folder path")
44+
parser.add_argument("--output", required=True, help="Output folder path")
45+
parser.add_argument("--size", type=int, default=256, help="Canvas size (default: 256)")
46+
47+
return parser.parse_args()
48+
49+
if __name__ == "__main__":
50+
args = parse_args()
51+
52+
pad_images(args.input, args.output, args.size)
11.5 KB
10.9 KB
9.23 KB
22.2 KB
36.6 KB
34.2 KB
30.6 KB

0 commit comments

Comments
 (0)