Texture_Converter.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Texture Converter.
  4. # Converts Minecraft resource packs to Minetest texture packs.
  5. # See README.md.
  6. __author__ = "Wuzzy"
  7. __license__ = "MIT License"
  8. __status__ = "Development"
  9. import shutil, csv, os, tempfile, sys, getopt
  10. # Helper vars
  11. home = os.environ["HOME"]
  12. mineclone2_path = home + "/.minetest/games/mineclone2"
  13. working_dir = os.getcwd()
  14. appname = "Texture_Converter.py"
  15. ### SETTINGS ###
  16. output_dir = working_dir
  17. base_dir = None
  18. # If True, will only make console output but not convert anything.
  19. dry_run = False
  20. # If True, textures will be put into a texture pack directory structure.
  21. # If False, textures will be put into MineClone 2 directories.
  22. make_texture_pack = True
  23. # If True, prints all copying actions
  24. verbose = False
  25. PXSIZE = 16
  26. syntax_help = appname+""" -i <input dir> [-o <output dir>] [-d] [-v|-q] [-h]
  27. Mandatory argument:
  28. -i <input directory>
  29. Directory of Minecraft resource pack to convert
  30. Optional arguments:
  31. -p <texture size>
  32. Specify the size (in pixels) of the original textures (default: 16)
  33. -o <output directory>
  34. Directory in which to put the resulting Minetest texture pack
  35. (default: working directory)
  36. -d
  37. Just pretend to convert textures and just print output, but do not actually
  38. change any files.
  39. -v
  40. Print out all copying actions
  41. -h
  42. Show this help and exit"""
  43. try:
  44. opts, args = getopt.getopt(sys.argv[1:],"hi:o:p:dv")
  45. except getopt.GetoptError:
  46. print(
  47. """ERROR! The options you gave me make no sense!
  48. Here's the syntax reference:""")
  49. print(syntax_help)
  50. sys.exit(2)
  51. for opt, arg in opts:
  52. if opt == "-h":
  53. print(
  54. """This is the official MineClone 2 Texture Converter.
  55. This will convert textures from Minecraft resource packs to
  56. a Minetest texture pack.
  57. Supported Minecraft version: 1.12 (Java Edition)
  58. Syntax:""")
  59. print(syntax_help)
  60. sys.exit()
  61. elif opt == "-d":
  62. dry_run = True
  63. elif opt == "-v":
  64. verbose = True
  65. elif opt == "-i":
  66. base_dir = arg
  67. elif opt == "-o":
  68. output_dir = arg
  69. elif opt == "-p":
  70. PXSIZE = int(arg)
  71. if base_dir == None:
  72. print(
  73. """ERROR: You didn't tell me the path to the Minecraft resource pack.
  74. Mind-reading has not been implemented yet.
  75. Try this:
  76. """+appname+""" -i <path to resource pack> -p <texture size>
  77. For the full help, use:
  78. """+appname+""" -h""")
  79. sys.exit(2);
  80. ### END OF SETTINGS ###
  81. tex_dir = base_dir + "/assets/minecraft/textures"
  82. # Get texture pack name (from directory name)
  83. bdir_split = base_dir.split("/")
  84. output_dir_name = bdir_split[-1]
  85. if len(output_dir_name) == 0:
  86. if len(bdir_split) >= 2:
  87. output_dir_name = base_dir.split("/")[-2]
  88. else:
  89. # Fallback
  90. output_dir_name = "New_MineClone_2_Texture_Pack"
  91. # FUNCTION DEFINITIONS
  92. def colorize(colormap, source, colormap_pixel, texture_size, destination):
  93. os.system("convert "+colormap+" -crop 1x1+"+colormap_pixel+" -depth 8 -resize "+texture_size+"x"+texture_size+" "+tempfile1.name)
  94. os.system("composite -compose Multiply "+tempfile1.name+" "+source+" "+destination)
  95. def colorize_alpha(colormap, source, colormap_pixel, texture_size, destination):
  96. colorize(colormap, source, colormap_pixel, texture_size, tempfile2.name)
  97. os.system("composite -compose Dst_In "+source+" "+tempfile2.name+" -alpha Set "+destination)
  98. # This function is unused atm.
  99. # TODO: Implemnt colormap extraction
  100. def extract_colormap(colormap, colormap_pixel, positions):
  101. os.system("convert -size 16x16 canvas:black "+tempfile1.name)
  102. x=0
  103. y=0
  104. for p in positions:
  105. os.system("convert "+colormap+" -crop 1x1+"+colormap_pixel+" -depth 8 "+tempfile2.name)
  106. os.system("composite -geometry 16x16+"+x+"+"+y+" "+tempfile2.name)
  107. x = x+1
  108. def target_dir(directory):
  109. if make_texture_pack:
  110. return output_dir + "/" + output_dir_name
  111. else:
  112. return mineclone2_path + directory
  113. # Copy texture files
  114. def convert_textures():
  115. failed_conversions = 0
  116. print("Texture conversion BEGINS NOW!")
  117. with open("Conversion_Table.csv", newline="") as csvfile:
  118. reader = csv.reader(csvfile, delimiter=",", quotechar='"')
  119. first_row = True
  120. for row in reader:
  121. # Skip first row
  122. if first_row:
  123. first_row = False
  124. continue
  125. src_dir = row[0]
  126. src_filename = row[1]
  127. dst_dir = row[2]
  128. dst_filename = row[3]
  129. if row[4] != "":
  130. xs = int(row[4])
  131. ys = int(row[5])
  132. xl = int(row[6])
  133. yl = int(row[7])
  134. xt = int(row[8])
  135. yt = int(row[9])
  136. else:
  137. xs = None
  138. blacklisted = row[10]
  139. if blacklisted == "y":
  140. # Skip blacklisted files
  141. continue
  142. if make_texture_pack == False and dst_dir == "":
  143. # If destination dir is empty, this texture is not supposed to be used in MCL2
  144. # (but maybe an external mod). It should only be used in texture packs.
  145. # Otherwise, it must be ignored.
  146. # Example: textures for mcl_supplemental
  147. continue
  148. src_file = base_dir + src_dir + "/" + src_filename # source file
  149. src_file_exists = os.path.isfile(src_file)
  150. dst_file = target_dir(dst_dir) + "/" + dst_filename # destination file
  151. if src_file_exists == False:
  152. print("WARNING: Source file does not exist: "+src_file)
  153. failed_conversions = failed_conversions + 1
  154. continue
  155. if xs != None:
  156. # Crop and copy images
  157. if not dry_run:
  158. os.system("convert "+src_file+" -crop "+xl+"x"+yl+"+"+xs+"+"+ys+" "+dst_file)
  159. if verbose:
  160. print(src_file + " → " + dst_file)
  161. else:
  162. # Copy image verbatim
  163. if not dry_run:
  164. shutil.copy2(src_file, dst_file)
  165. if verbose:
  166. print(src_file + " → " + dst_file)
  167. # Convert chest textures (requires ImageMagick)
  168. chest_files = [
  169. [ tex_dir + "/entity/chest/normal.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "default_chest_top.png", "mcl_chests_chest_bottom.png", "default_chest_front.png", "mcl_chests_chest_left.png", "mcl_chests_chest_right.png", "mcl_chests_chest_back.png" ],
  170. [ tex_dir + "/entity/chest/trapped.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_chest_trapped_top.png", "mcl_chests_chest_trapped_bottom.png", "mcl_chests_chest_trapped_front.png", "mcl_chests_chest_trapped_left.png", "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_back.png" ],
  171. [ tex_dir + "/entity/chest/ender.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_ender_chest_top.png", "mcl_chests_ender_chest_bottom.png", "mcl_chests_ender_chest_front.png", "mcl_chests_ender_chest_left.png", "mcl_chests_ender_chest_right.png", "mcl_chests_ender_chest_back.png" ]
  172. ]
  173. for c in chest_files:
  174. chest_file = c[0]
  175. if os.path.isfile(chest_file):
  176. PPX = (PXSIZE/16)
  177. CHPX = (PPX * 14) # Chest width
  178. LIDPX = (PPX * 5) # Lid height
  179. LIDLOW = (PPX * 10) # Lower lid section height
  180. LOCKW = (PPX * 6) # Lock width
  181. LOCKH = (PPX * 5) # Lock height
  182. cdir = c[1]
  183. top = cdir + "/" + c[2]
  184. bottom = cdir + "/" + c[3]
  185. front = cdir + "/" + c[4]
  186. left = cdir + "/" + c[5]
  187. right = cdir + "/" + c[6]
  188. back = cdir + "/" + c[7]
  189. # Top
  190. os.system("convert " + chest_file + " \
  191. \( -clone 0 -crop "+str(CHPX)+"x"+str(CHPX)+"+"+str(CHPX)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX)+"x"+str(CHPX)+" "+top)
  192. # Bottom
  193. os.system("convert " + chest_file + " \
  194. \( -clone 0 -crop "+str(CHPX)+"x"+str(CHPX)+"+"+str(CHPX*2)+"+"+str(CHPX+LIDPX)+" \) -geometry +0+0 -composite -extent "+str(CHPX)+"x"+str(CHPX)+" "+bottom)
  195. # Front
  196. os.system("convert " + chest_file + " \
  197. \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(CHPX)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
  198. \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(CHPX)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
  199. -extent "+str(CHPX)+"x"+str(CHPX)+" "+front)
  200. # TODO: Add lock
  201. # Left, right back (use same texture, we're lazy
  202. files = [ left, right, back ]
  203. for f in files:
  204. os.system("convert " + chest_file + " \
  205. \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(0)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
  206. \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(0)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
  207. -extent "+str(CHPX)+"x"+str(CHPX)+" "+f)
  208. # Double chests
  209. chest_files = [
  210. [ tex_dir + "/entity/chest/normal_double.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "default_chest_front_big.png", "default_chest_top_big.png", "default_chest_side_big.png" ],
  211. [ tex_dir + "/entity/chest/trapped_double.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_chest_trapped_front_big.png", "mcl_chests_chest_trapped_top_big.png", "mcl_chests_chest_trapped_side_big.png" ]
  212. ]
  213. for c in chest_files:
  214. chest_file = c[0]
  215. if os.path.isfile(chest_file):
  216. PPX = (PXSIZE/16)
  217. CHPX = (PPX * 14) # Chest width (short side)
  218. CHPX2 = (PPX * 15) # Chest width (long side)
  219. LIDPX = (PPX * 5) # Lid height
  220. LIDLOW = (PPX * 10) # Lower lid section height
  221. LOCKW = (PPX * 6) # Lock width
  222. LOCKH = (PPX * 5) # Lock height
  223. cdir = c[1]
  224. front = cdir + "/" + c[2]
  225. top = cdir + "/" + c[3]
  226. side = cdir + "/" + c[4]
  227. # Top
  228. os.system("convert " + chest_file + " \
  229. \( -clone 0 -crop "+str(CHPX2)+"x"+str(CHPX)+"+"+str(CHPX)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX2)+"x"+str(CHPX)+" "+top)
  230. # Front
  231. # TODO: Add lock
  232. os.system("convert " + chest_file + " \
  233. \( -clone 0 -crop "+str(CHPX2)+"x"+str(LIDPX)+"+"+str(CHPX)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
  234. \( -clone 0 -crop "+str(CHPX2)+"x"+str(LIDLOW)+"+"+str(CHPX)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
  235. -extent "+str(CHPX2)+"x"+str(CHPX)+" "+front)
  236. # Side
  237. os.system("convert " + chest_file + " \
  238. \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(0)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
  239. \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(0)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
  240. -extent "+str(CHPX)+"x"+str(CHPX)+" "+side)
  241. # Generate railway crossings and t-junctions. Note: They may look strange.
  242. # Note: these may be only a temporary solution, as crossings and t-junctions do not occour in MC.
  243. # TODO: Curves
  244. rails = [
  245. # (Straigt src, curved src, t-junction dest, crossing dest)
  246. ("rail_normal.png", "rail_normal_turned.png", "default_rail_t_junction.png", "default_rail_crossing.png"),
  247. ("rail_golden.png", "rail_normal_turned.png", "carts_rail_t_junction_pwr.png", "carts_rail_crossing_pwr.png"),
  248. ("rail_golden_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"),
  249. ("rail_detector.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"),
  250. ("rail_detector_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"),
  251. ("rail_activator.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"),
  252. ("rail_activator_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_d_t_junction.png", "mcl_minecarts_rail_activator_powered_crossing.png"),
  253. ]
  254. for r in rails:
  255. os.system("composite -compose Dst_Over "+tex_dir+"/blocks/"+r[0]+" "+tex_dir+"/blocks/"+r[1]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r[2])
  256. os.system("convert "+tex_dir+"/blocks/"+r[0]+" -rotate 90 "+tempfile1.name)
  257. os.system("composite -compose Dst_Over "+tempfile1.name+" "+tex_dir+"/blocks/"+r[0]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r[3])
  258. # Convert banner overlays
  259. overlays = [
  260. "base",
  261. "border",
  262. "bricks",
  263. "circle",
  264. "creeper",
  265. "cross",
  266. "curly_border",
  267. "diagonal_left",
  268. "diagonal_right",
  269. "diagonal_up_left",
  270. "diagonal_up_right",
  271. "flower",
  272. "gradient",
  273. "gradient_up",
  274. "half_horizontal_bottom",
  275. "half_horizontal",
  276. "half_vertical",
  277. "half_vertical_right",
  278. "rhombus",
  279. "mojang",
  280. "skull",
  281. "small_stripes",
  282. "straight_cross",
  283. "stripe_bottom",
  284. "stripe_center",
  285. "stripe_downleft",
  286. "stripe_downright",
  287. "stripe_left",
  288. "stripe_middle",
  289. "stripe_right",
  290. "stripe_top",
  291. "square_bottom_left",
  292. "square_bottom_right",
  293. "square_top_left",
  294. "square_top_right",
  295. "triangle_bottom",
  296. "triangles_bottom",
  297. "triangle_top",
  298. "triangles_top",
  299. ]
  300. for o in overlays:
  301. orig = tex_dir + "/entity/banner/" + o + ".png"
  302. if os.path.isfile(orig):
  303. if o == "mojang":
  304. o = "thing"
  305. dest = target_dir("/mods/ITEMS/mcl_banners/textures")+"/"+"mcl_banners_"+o+".png"
  306. os.system("convert "+orig+" -transparent-color white -background black -alpha remove -alpha copy -channel RGB -white-threshold 0 "+dest)
  307. # Convert grass
  308. grass_file = tex_dir + "/blocks/grass_top.png"
  309. if os.path.isfile(grass_file):
  310. FOLIAG = tex_dir+"/colormap/foliage.png"
  311. GRASS = tex_dir+"/colormap/grass.png"
  312. # Leaves
  313. colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_oak.png", "116+143", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_leaves.png")
  314. colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_big_oak.png", "158+177", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_big_oak.png")
  315. colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_acacia.png", "40+255", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_acacia_leaves.png")
  316. colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_spruce.png", "226+230", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_spruce.png")
  317. colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_birch.png", "141+186", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_birch.png")
  318. colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_jungle.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_jungleleaves.png")
  319. # Waterlily
  320. colorize_alpha(FOLIAG, tex_dir+"/blocks/waterlily.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/flowers_waterlily.png")
  321. # Vines
  322. colorize_alpha(FOLIAG, tex_dir+"/blocks/vine.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_vine.png")
  323. # Tall grass, fern (inventory images)
  324. pcol = "49+172" # Plains grass color
  325. colorize_alpha(GRASS, tex_dir+"/blocks/tallgrass.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_tallgrass_inv.png")
  326. colorize_alpha(GRASS, tex_dir+"/blocks/fern.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_fern_inv.png")
  327. colorize_alpha(GRASS, tex_dir+"/blocks/double_plant_fern_top.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_double_plant_fern_inv.png")
  328. colorize_alpha(GRASS, tex_dir+"/blocks/double_plant_grass_top.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_double_plant_grass_inv.png")
  329. # TODO: Convert grass palette
  330. offset = [
  331. [ pcol, "", "grass" ], # Default grass: Plains
  332. ]
  333. for o in offset:
  334. colorize(GRASS, tex_dir+"/blocks/grass_top.png", o[0], str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o[2]+".png")
  335. colorize_alpha(GRASS, tex_dir+"/blocks/grass_side_overlay.png", o[0], str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o[2]+"_side.png")
  336. # Metadata
  337. if make_texture_pack:
  338. # Create description file
  339. description = "Texture pack for MineClone 2. Automatically converted from a Minecraft resource pack by the MineClone 2 Texture Converter. Size: "+str(PXSIZE)+"×"+str(PXSIZE)
  340. description_file = open(target_dir("/") + "/description.txt", "w")
  341. description_file.write(description)
  342. description_file.close()
  343. # Create preview image (screenshot.png)
  344. os.system("convert -size 300x200 canvas:transparent "+target_dir("/") + "/screenshot.png")
  345. os.system("composite "+base_dir+"/pack.png "+target_dir("/") + "/screenshot.png -gravity center "+target_dir("/") + "/screenshot.png")
  346. print("Textures conversion COMPLETE!")
  347. if failed_conversions > 0:
  348. print("WARNING: Number of missing files in original resource pack: "+str(failed_conversions))
  349. print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
  350. if make_texture_pack:
  351. print("You can now retrieve the texture pack in "+output_dir+"/"+output_dir_name+"/")
  352. # ENTRY POINT
  353. if make_texture_pack and not os.path.isdir(output_dir+"/"+output_dir_name):
  354. os.mkdir(output_dir+"/"+output_dir_name)
  355. tempfile1 = tempfile.NamedTemporaryFile()
  356. tempfile2 = tempfile.NamedTemporaryFile()
  357. convert_textures()
  358. tempfile1.close()
  359. tempfile2.close()