anim_ingest.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import io
  2. from PIL import Image
  3. from bs4 import BeautifulSoup
  4. from colorspacious import cspace_convert
  5. import requests
  6. import numpy as np
  7. import ingest
  8. base = "https://play.pokemonshowdown.com/sprites/ani/"
  9. back_base = "https://play.pokemonshowdown.com/sprites/ani-back/"
  10. # removing all forms of a pokemon, and also pokestars
  11. start_with_filters = [
  12. # no significant visual changes
  13. "arceus-", "silvally-", "genesect-", "pumpkaboo-", "gourgeist-", "unown-", "giratina-",
  14. # cannot start the battle in alternate form
  15. "castform-", "cherrim-", "aegislash-", "xerneas-", "wishiwashi-", "eiscue-", "mimikyu-",
  16. "cramorant-", "morepeko-",
  17. # weird event thing
  18. "greninja-", "eevee-", "pikachu-", "zarude-", "magearna-",
  19. # pokestars
  20. "pokestar",
  21. ]
  22. # removing all forms of a type
  23. end_with_filters = [
  24. "-mega.gif", "-megax.gif", "-megay.gif", "-primal.gif", "-ultra.gif",
  25. "-gmax.gif", "-eternamax.gif", "-totem.gif", "-f.gif", "-b.gif",
  26. ]
  27. # removing pokemon entirely
  28. full_filters = [
  29. # darmanitan zen forms (cannot start in zen)
  30. "darmanitan-galarzen.gif", "darmanitan-zen.gif",
  31. # minior core forms (cannot start in anything but -meteor, renamed below)
  32. "minior.gif", "minior-blue.gif", "minior-green.gif", "minior-indigo.gif",
  33. "minior-orange.gif", "minior-violet.gif", "minior-yellow.gif",
  34. # because it is a create-a-pokemon
  35. "astrolotl.gif", "aurumoth.gif", "caribolt.gif", "cawmodore.gif", "chromera.gif", "crucibelle.gif",
  36. "equilibra.gif", "fidgit.gif", "jumbao.gif", "justyke.gif", "kerfluffle.gif", "kitsunoh.gif",
  37. "krilowatt.gif", "malaconda.gif", "miasmaw.gif", "mollux.gif", "naviathan.gif", "necturna.gif",
  38. "pajantom.gif", "plasmanta.gif", "pluffle.gif", "protowatt.gif", "scratchet.gif", "smogecko.gif",
  39. "smoguana.gif", "smokomodo.gif", "snaelstrom.gif", "stratagem.gif", "tomohawk.gif", "volkraken.gif", "voodoom.gif",
  40. # typos/duplicates
  41. "buffalant.gif", "klinklang-back.gif", "krikretot.gif", "pumpkabo-super.gif", "magcargo%20.gif",
  42. "ratatta-a.gif", "ratatta-alola.gif", "raticate-a.gif",
  43. "rotom-h.gif", "rotom-m.gif", "rotom-s.gif", "rotom-w.gif",
  44. # not a pokemon
  45. "substitute.gif",
  46. ]
  47. # force certain pokemon to stay
  48. force_keep = [ "meowstic-f.gif" ]
  49. # rename certain pokemon after the fact
  50. rename = {
  51. # dash consistency
  52. "nidoranm": "nidoran-m",
  53. "nidoranf": "nidoran-f",
  54. "porygonz": "porygon-z",
  55. "tapubulu": "tapu-bulu",
  56. "tapufini": "tapu-fini",
  57. "tapukoko": "tapu-koko",
  58. "tapulele": "tapu-lele",
  59. "hooh": "ho-oh",
  60. "mimejr": "mime-jr",
  61. "mrmime": "mr-mime",
  62. "mrmime-galar": "mr-mime-galar",
  63. "mrrime": "mr-rime",
  64. "jangmoo": "jangmo-o",
  65. "hakamoo": "hakamo-o",
  66. "kommoo": "kommo-o",
  67. "typenull": "type-null",
  68. "oricorio-pompom": "oricorio-pom-pom",
  69. "necrozma-duskmane": "necrozma-dusk-mane",
  70. "necrozma-dawnwings": "necrozma-dawn-wings",
  71. "toxtricity-lowkey": "toxtricity-low-key",
  72. # rename forms
  73. "shellos": "shellos-west",
  74. "shaymin": "shaymin-land",
  75. "meloetta": "meloetta-aria",
  76. "keldeo": "keldeo-ordinary",
  77. "hoopa": "hoopa-confined",
  78. "burmy": "burmy-plant",
  79. "wormadam": "wormadam-plant",
  80. "deerling": "deerling-spring",
  81. "sawsbuck": "sawsbuck-spring",
  82. "vivillon": "vivillon-meadow",
  83. "basculin": "basculin-redstriped",
  84. "meowstic": "meowstic-male",
  85. "meowstic-f": "meowstic-female",
  86. "flabebe": "flabebe-red",
  87. "floette": "floette-red",
  88. "florges": "florges-red",
  89. "minior-meteor": "minior",
  90. "sinistea": "sinistea-phony",
  91. "polteageist": "polteageist-phony",
  92. "gastrodon": "gastrodon-west",
  93. "furfrou": "furfrou-natural",
  94. "wishiwashi": "wishiwashi-school",
  95. "tornadus": "tornadus-incarnate",
  96. "landorus": "landorus-incarnate",
  97. "thundurus": "thundurus-incarnate",
  98. "calyrex-ice": "calyrex-ice-rider",
  99. "calyrex-shadow": "calyrex-shadow-rider",
  100. "urshifu-rapidstrike": "urshifu-rapid-strike",
  101. "zacian": "zacian-hero",
  102. "zamazenta": "zamazenta-hero",
  103. }
  104. def get_all_pokemon() -> list[str]:
  105. soup = BeautifulSoup(requests.get(back_base).text, "html.parser")
  106. gifs = [href for a in soup.find_all("a") if (href := a.get("href")).endswith("gif")]
  107. return [
  108. g[:-4]
  109. for g in gifs
  110. if g in force_keep or (
  111. g not in full_filters
  112. and not any(g.startswith(f) for f in start_with_filters)
  113. and not any(g.endswith(f) for f in end_with_filters)
  114. )
  115. ]
  116. def load_image(base: str, name: str) -> Image:
  117. return Image.open(io.BytesIO(requests.get(base + name + ".gif").content))
  118. def get_all_pixels(im: Image) -> list[tuple[int, int, int]]:
  119. rgb_pixels = []
  120. for fr in range(getattr(im, "n_frames", 1)):
  121. im.seek(fr)
  122. rgb_pixels += [
  123. (r, g, b)
  124. for r, g, b, a in im.convert("RGBA").getdata()
  125. if not ingest.is_outline(r, g, b, a)
  126. ]
  127. return rgb_pixels
  128. if __name__ == "__main__":
  129. pkmn = get_all_pokemon()
  130. print("Found", len(pkmn), "sprites...")
  131. errors = []
  132. with open("database-anim.js", "w") as outfile:
  133. outfile.write("const databaseV2 = [\n")
  134. for name in pkmn:
  135. print("Ingesting", name, "...")
  136. try:
  137. front = get_all_pixels(load_image(base, name))
  138. back = get_all_pixels(load_image(back_base, name))
  139. rgb_pixels = np.array(front + back)
  140. jab_pixels = cspace_convert(rgb_pixels, "sRGB255", "CAM02-UCS")
  141. stats = [len(rgb_pixels), *ingest.all_stats(jab_pixels), *ingest.all_stats(rgb_pixels)]
  142. outfile.write(f' [ "{rename.get(name, name)}", {", ".join(str(n) for n in stats)} ],\n')
  143. except Exception as e:
  144. print(e)
  145. errors.append(name)
  146. outfile.write("];\n")
  147. print("Errors:", errors)