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