The following makes these assumptions for laziness reasons, the code could be improved to not make them:
- Only interested in recipe layered icons that are entirely composed of icons provided by a specific mod
- Tint colours are specified in the 0-1 range only
- All of the icon dimensions used are assumed to be the same size in the entire set of recipes
First you do a dump from data-final-fixes, looking for any recipe that has an icons definition that meets the criteria. Dump delimited string info about those icon layers to the log.
Code: Select all
local output = "\n\nICONDUMP>>>\n" local modname = "__MyModName__" for _,recipe in pairs(data.raw.recipe) do if recipe.icons then local nope = false for _,icon in pairs(recipe.icons) do if not string.find(icon.icon,modname) then nope = true end end if not nope then -- output icons data output = output .. recipe.name .. "@" for _,icon in pairs(recipe.icons) do output = output .. icon.icon .."#".. icon.icon_size .."#".. (icon.scale or "1") output = output .."#".. (icon.shift and icon.shift or "0") .."#".. (icon.shift and icon.shift or "0") output = output .."#".. (icon.tint and (icon.tint.r or icon.tint) or "1") output = output .."#".. (icon.tint and (icon.tint.g or icon.tint) or "1") output = output .."#".. (icon.tint and (icon.tint.b or icon.tint) or "1") output = output .."#".. (icon.tint and (icon.tint.a or icon.tint) or "1") output = output .."@" end output = output .. "\n" end end end log(output.."<<<ICONDUMP")
Code: Select all
from PIL import Image,ImageChops import os from pathlib import Path fbaked = "./baked/" foutput = "./final/" mod_name = "__MyModName__" mod_path = "C:/Users/BillGates/Games/Factorio/mods/MyModName_1.0.0" log_path = "C:/Users/BillGates/AppData/Roaming/Factorio" assumed_size = 64 mipmap_levels = 4 def tint_image(image, tint_color): return ImageChops.multiply(image, Image.new("RGBA", image.size, tint_color)) def create_mipmap(outputf, inputf, size, levels): original = Image.open(inputf) mipmap = Image.new("RGBA", (int(size * ((1-0.5**levels)/0.5)), size), color = (0,0,0,0)) offset = 0 for i in range(0,levels): new_size = int(size * (0.5**i)) copy = original.resize((new_size, new_size), Image.LANCZOS) mipmap.paste(copy, box = (offset, 0)) offset += new_size mipmap.save(outputf) file = open(log_path+"/factorio-current.log","r") fudgefactor = 32/assumed_size read = False for line in file: if "ICONDUMP>>>" in line: read = True elif "<<<ICONDUMP" in line: read = False elif read: icons = line.split("@") recipe = icons output = Image.new("RGBA",(assumed_size,assumed_size), color = (0,0,0,0)) for i in range(1,len(icons)-1): filename, size, scale, shift_x, shift_y, r, g, b, a = icons[i].split("#") filename = filename.replace(mod_name,mod_path) icon = Image.open(filename).convert("RGBA").crop((0,0,63,63)) icon = tint_image(icon,(int(float(r)*255),int(float(g)*255),int(float(b)*255),int(float(a)*255))) size = int(size) scale = float(scale) shift_x = float(shift_x)/fudgefactor shift_y = float(shift_y)/fudgefactor if scale != 1: scale = scale / fudgefactor new_size = int(assumed_size*scale) icon = icon.resize((new_size,new_size),Image.LANCZOS) shift_x = shift_x + int((assumed_size - new_size) * fudgefactor) shift_y = shift_y + int((assumed_size - new_size) * fudgefactor) layer = Image.new("RGBA",(assumed_size,assumed_size), color = (0,0,0,0)) layer.paste(icon,(int(shift_x),int(shift_y)),icon) output = Image.alpha_composite(output,layer) print("Baking "+recipe+" ...") output.save(fbaked+recipe+".png") if mipmap_levels > 1: for dirpath, dirs, files in os.walk(fbaked): for filename in files: if ".png" in filename: print("Mipmapping "+filename+" ...") create_mipmap(foutput+filename, fbaked+filename, assumed_size, mipmap_levels)
Code: Select all
local modname = "__MyModName__" for _,recipe in pairs(data.raw.recipe) do if recipe.icons then local nope = false for _,icon in pairs(recipe.icons) do if not string.find(icon.icon,modname) then nope = true end end if not nope then -- replace icons recipe.icon = MY_MOD.get_path_to_icon(recipe.name,"baked/recipe") recipe.icon_size = MY_MOD.default_icon_size recipe.icon_mipmaps = 4 recipe.icons = nil end end end
An inventory tab full of disabled layered icons, without "baking":
How it looks with the baked layers (it's a subtle difference but some of us are subtle people - compare the top row, fourth from the left for the most obvious one):
Proof that it works with shift and tint as well:
- You can have layered item and recipe icons without a care in the world, using the data in your mod to generate layered icon layouts and then "locking" them into a single space and runtime-efficient icon
- You can see things more easily in the player crafting tab if you are old and half-blind like I am
- If applied to items, prevents potential FPS losses from having thousands of layered icons moving around on belts
- If you are generating icons procedurally (aren't we all) and recipe changes or anything changes in your mod that changes the icons, the scripts won't know and the "baked" icon stays the same until you run it all over again
- If you introduce new recipes that meet the criteria that haven't been baked yet, the mod won't even load - might be an especial issue for combinations of mods that modify each other conditionally. Since we have no way of detecting the existence of a mod asset file like sprites and sounds, we can't "skip" the replacement. You could maybe mitigate this sort of thing by flagging the prototypes with custom fields but if it's mods by different random authors that are changing each other that's not going to help much.
Those limitations aside, I think it's nifty. This could easily be extended to item icons as well. You could also extend the layering to add things like drop shadows or outlines, etc., knowing that they won't be messed up by transparency in multiple layers in the crafting GUIs.
Keeps me off the streets.