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[1] or "0") .."#".. (icon.shift and icon.shift[2] or "0")
output = output .."#".. (icon.tint and (icon.tint.r or icon.tint[1]) or "1")
output = output .."#".. (icon.tint and (icon.tint.g or icon.tint[2]) or "1")
output = output .."#".. (icon.tint and (icon.tint.b or icon.tint[3]) or "1")
output = output .."#".. (icon.tint and (icon.tint.a or icon.tint[4]) 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[0]
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:
The benefits:
- 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
The drawbacks:
- 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.