"Baking" layered recipe icons (Python)

Enhance Your Overall Game-Play.
Mod-Database, Calculators, Cheatsheets, Multiplayer, Scripts, Libs and Other Useful Stuff.
Post Reply
User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 1997
Joined: Fri Nov 06, 2015 7:41 pm

"Baking" layered recipe icons (Python)

Post by Deadlock989 »

Scripts (Lua for the Factorio data stage, Python for the icon compositing) that convert layered recipe icons into a single layer icon, taking shifts, scale and tints into account. Could be easily adapted for items instead of recipes.

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")
The following Python script (PIL library required) will look in the most current Factorio log (given its location) for your icon layer dumps and reconstruct the images they define, saving the baked square icon output into one folder and a mipmapped version into another.

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)
Move the final output to a location within your mod. Return to data-final-fixes, disable the data dump, and run something like the following instead (I already had functions to get the icon path, you would have to specify it your own way). You could wrap these Lua snippets in boolean constants or tie them to a mod setting if you wanted, allowing you to switch your mod back and forth between "use dynamic layers" and "use baked single-layer" modes.

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
And you're done.

An inventory tab full of disabled layered icons, without "baking":

without.jpg
without.jpg (72.71 KiB) Viewed 890 times
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):

with.jpg
with.jpg (71.92 KiB) Viewed 890 times

Proof that it works with shift and tint as well:

tint'n'shift.jpg
tint'n'shift.jpg (114.02 KiB) Viewed 890 times
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.
Last edited by Deadlock989 on Tue Mar 17, 2020 6:36 pm, edited 15 times in total.

User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 1997
Joined: Fri Nov 06, 2015 7:41 pm

Re: "Baking" layered recipe and item icons (Python)

Post by Deadlock989 »

Procedural drop shadow with regular layered icons - because the higher layers are partly transparent, the shadow underneath bleeds through, blackening the whole overlay, and you can see the bottom layer showing through:
without.jpg
without.jpg (24.69 KiB) Viewed 872 times

"Baked", the issue goes away:
with.jpg
with.jpg (24.42 KiB) Viewed 872 times

User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 1997
Joined: Fri Nov 06, 2015 7:41 pm

Re: "Baking" layered recipe and item icons (Python)

Post by Deadlock989 »

Updated OP with an improved script which handles alpha compositing better and can also produce mipmapped output.

mipmap.png
mipmap.png (85.73 KiB) Viewed 843 times

billbo99
Fast Inserter
Fast Inserter
Posts: 119
Joined: Fri Nov 02, 2018 9:19 am
Contact:

Re: "Baking" layered recipe and item icons (Python)

Post by billbo99 »

In my own experience I have noticed that not all recipes have icons. If the mod owner has not defined on the game will work out an icon based on the main_product.

@theRustyKnife has made this helper library mod .. https://mods.factorio.com/mod/rusty-locale .. which has taken over a lot of the hard work. If you look at the home page for the mod, there are methods to get the correct localised_name and icons for a prototype.

billbo99
Fast Inserter
Fast Inserter
Posts: 119
Joined: Fri Nov 02, 2018 9:19 am
Contact:

Re: "Baking" layered recipe and item icons (Python)

Post by billbo99 »

As for layered icons its ODD that the game does not merge all the layers together when it forms the atlas images, after that point the icons are not going to change.

I wonder if we could poke WUBE into do some optimization and instead of relying on the graphics cards to layer all the icons as they are displayed on the map, pre-bake them during the process that builds the atlas.

PS Maybe they have already done this optimization, but have not talked about it.

User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 1997
Joined: Fri Nov 06, 2015 7:41 pm

Re: "Baking" layered recipe and item icons (Python)

Post by Deadlock989 »

billbo99 wrote:
Mon Mar 02, 2020 8:36 am
As for layered icons its ODD that the game does not merge all the layers together when it forms the atlas images, after that point the icons are not going to change.

I wonder if we could poke WUBE into do some optimization and instead of relying on the graphics cards to layer all the icons as they are displayed on the map, pre-bake them during the process that builds the atlas.

PS Maybe they have already done this optimization, but have not talked about it.
They have not. It has been discussed. Along with various requests to build various image manipulation tools like hue and saturation shifts into the game's icon compositing. I doubt the latter is ever happening. The former, maybe, but there's little use case in vanilla (just barrelling and that's it).

For everything else, I understand Wube's historic position as being "make your own". But most modders won't be bothered, give them a dodgy shortcut, they will spam it.

My problem has been that the data to make composite icons is (a) only contained in Lua tables where it's hard to get at from any other software I use and (b) constantly changes as I work on the mod. Hence the desire to have an automated way (this is Factorio) to "bake" icons down based on a mod's data prototypes as procedurally generated. I don't think it was predicted that people would start taking the barrelling approach to other icons where there is more than one recipe that leads to a product, sometimes in large quantities (e.g. multi-stage ore processing).

That said, I wouldn't have bothered with any of this if disabled crafting icons weren't semi-transparent. It doesn't really matter for performance if recipe icons are layered - it can matter for items though.

User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 1997
Joined: Fri Nov 06, 2015 7:41 pm

Re: "Baking" layered recipe and item icons (Python)

Post by Deadlock989 »

Updated OP with versions that look in your current Factorio log automatically, instead of having to locate and copy/paste the dump into a different file.

User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 1997
Joined: Fri Nov 06, 2015 7:41 pm

Re: "Baking" layered recipe icons (Python)

Post by Deadlock989 »

With the new character gui in 0.18.13, 95% of the point of these scripts is happily a moot point, since the crafting GUIs make less frequent use of transparency in layers now:

red.png
red.png (153.21 KiB) Viewed 585 times
However there is still a bit of transparency in the rarer cases where a recipe is hand-craftable but you don't currently have the ingredients for it. Lower layers in recipe icons will still "bleed through" in those cases, although you can turn this off in the utility constants (disabled_recipe_slot_tint).

Also, if you want really mind-numbing attention to detail, the "shadows" (black glow) around the icons are only based on the base layer, which can even be blank in some use cases - so baking the recipe icon would get you a shadow-outline over the entire silhouette.

Also, the script can still easily be adapted for items, which really should avoid having layers unless it's an item that will never, ever be shipped around on belts in large quantities.

Post Reply

Return to “Tools”

Who is online

Users browsing this forum: No registered users