sourceSets.main.java.srcDirs = ["src/"] import arc.files.Fi import arc.files.ZipFi import arc.func.Func2 import arc.graphics.Color import arc.graphics.Pixmap import arc.packer.TexturePacker import arc.struct.ObjectMap import arc.struct.OrderedMap import arc.struct.Seq import arc.util.Http import arc.util.Log import arc.util.OS import arc.util.Threads import arc.util.io.PropertiesUtils import arc.util.io.Streams import java.util.concurrent.ExecutorService import java.util.concurrent.Executors def genFolder = "../core/assets-raw/sprites_out/generated/" def enableAA = true @groovy.transform.CompileStatic static int getRGB(Pixmap image, int ix, int iy) { return image.getRaw(Math.max(Math.min(ix, image.width - 1), 0), Math.max(Math.min(iy, image.height - 1), 0)) } @groovy.transform.CompileStatic static void antialias(File file){ Pixmap image = new Pixmap(new Fi(file)) Pixmap out = image.copy() Color color = new Color() Color sum = new Color() Color suma = new Color() int[] p = new int[9] for(int x = 0; x < image.width; x++){ for(int y = 0; y < image.height; y++){ int A = getRGB(image, x - 1, y + 1), B = getRGB(image, x, y + 1), C = getRGB(image, x + 1, y + 1), D = getRGB(image, x - 1, y), E = getRGB(image, x, y), F = getRGB(image, x + 1, y), G = getRGB(image, x - 1, y - 1), H = getRGB(image, x, y - 1), I = getRGB(image, x + 1, y - 1) Arrays.fill(p, E) if(D == B && D != H && B != F) p[0] = D if((D == B && D != H && B != F && E != C) || (B == F && B != D && F != H && E != A)) p[1] = B if(B == F && B != D && F != H) p[2] = F if((H == D && H != F && D != B && E != A) || (D == B && D != H && B != F && E != G)) p[3] = D if((B == F && B != D && F != H && E != I) || (F == H && F != B && H != D && E != C)) p[5] = F if(H == D && H != F && D != B) p[6] = D if((F == H && F != B && H != D && E != G) || (H == D && H != F && D != B && E != I)) p[7] = H if(F == H && F != B && H != D) p[8] = F suma.set(0) for(int val : p){ color.rgba8888(val) color.premultiplyAlpha() suma.r(suma.r + color.r) suma.g(suma.g + color.g) suma.b(suma.b + color.b) suma.a(suma.a + color.a) } float fm = suma.a <= 0.001f ? 0f : (float)(1f / suma.a) suma.mul(fm, fm, fm, fm) float total = 0 sum.set(0) for(int val : p){ color.rgba8888(val) float a = color.a color.lerp(suma, (float) (1f - a)) sum.r(sum.r + color.r) sum.g(sum.g + color.g) sum.b(sum.b + color.b) sum.a(sum.a + a) total += 1f } fm = (float)(1f / total) sum.mul(fm, fm, fm, fm) out.setRaw(x, y, sum.rgba8888()) sum.set(0) } } image.dispose() out.dispose() new Fi(file).writePng(out) } task antialiasImages(){ doLast{ for(def img : project.getProperty("images").split(",")){ println(project.getProperty("startdir") + "/" + img) antialias(new File(project.getProperty("startdir") + "/" + img)) } } } task tileImages(){ doLast{ for(def img : project.getProperty("images").split(",")){ println(project.getProperty("startdir") + "/" + img) tileImage(new File(project.getProperty("startdir") + "/" + img)) } } } task pack(dependsOn: [classes, configurations.runtimeClasspath]){ doLast{ //cleanup old sprites delete{ delete "../core/assets-raw/sprites_out/" } //copy in new sprites copy{ from "../core/assets-raw/sprites/" into "../core/assets-raw/sprites_out/" } //run generation task; generate all needed sprites file(genFolder).mkdirs() javaexec{ main = "mindustry.tools.ImagePacker" classpath = sourceSets.main.runtimeClasspath workingDir = genFolder } copy{ from "../core/assets-raw/sprites_out/ui/icons" into "../core/assets-raw/sprites_out/ui/" } delete{ delete "../core/assets-raw/sprites_out/ui/icons" } if(enableAA){ ExecutorService executor = Executors.newFixedThreadPool(OS.cores) long ms = System.currentTimeMillis() //antialias everything except UI elements fileTree(dir: new File(rootDir, 'core/assets-raw/sprites_out/').absolutePath, include: "**/*.png").visit{ file -> if(file.isDirectory() || (file.toString().replace("\\", "/").contains("/ui/") && file.toString().startsWith("icon-")) || file.toString().contains(".9.png") || file.toString().contains("aaaa")) return executor.submit{ antialias(file.file) } } Threads.await(executor) println "Time taken for AA: ${(System.currentTimeMillis() - ms) / 1000f} seconds" } println("\n\nPacking normal 4096 sprites...\n\n") //pack normal sprites TexturePacker.process(new File(rootDir, "core/assets-raw/sprites_out/").absolutePath, new File(rootDir, "core/assets/sprites/").absolutePath, "sprites.aatls") println("\n\nPacking fallback 2048 sprites...\n\n") //replace config file contents fileTree(dir: '../core/assets-raw/sprites_out/', include: "**/*.json").visit{ file -> if(!file.isDirectory()) file.file.text = file.file.text.replace("4096", "2048") } //pack fallback 2048x2048 sprites - disabled when debugging if(!project.hasProperty("args")){ TexturePacker.process(new File(rootDir, "core/assets-raw/sprites_out/").absolutePath, new File(rootDir, "core/assets/sprites/fallback/").absolutePath, "sprites.aatls") } } } task genSprites(dependsOn: classes, type: JavaExec){ finalizedBy 'antialiasGen' mainClass = "mindustry.tools.ImagePacker" classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = genFolder } task fontgen(dependsOn: classes, type: JavaExec){ /* icon font pipeline: 1. take set of pre-defined icons and SVGs 2. use Fontello API to get a font with these 3. combine fontello font and standard font, get output font 4. use json to generate a file with constants for every icon size+type (during annotation processing) */ doLast{ Fi folder = Fi.get("core/assets-raw/fontgen/out/") folder.mkdirs() Log.info("Session...") OS.exec("curl", "--fail", "--output", "core/assets-raw/fontgen/out/session", "--form", "config=@core/assets-raw/fontgen/config.json", "https://fontello.com") Log.info("Zip...") String session = folder.child("session").readString() Http.get("https://fontello.com/" + session + "/get").block(result -> { Streams.copy(result.getResultAsStream(), folder.child("font.zip").write()) }) Log.info("Icon font...") ZipFi zip = new ZipFi(folder.child("font.zip")) Fi dest = folder.child("font.woff") zip.list()[0].child("font").child("fontello.ttf").copyTo(dest) dest.copyTo(Fi.get("core/assets/fonts/icon.ttf")) Log.info("Merge...") //TODO this is broken Log.info(OS.exec("fontforge", "-script", Fi.get("core/assets-raw/fontgen/merge.pe").absolutePath(), Fi.get("core/assets/fonts/font.woff").absolutePath(), Fi.get("core/assets-raw/fontgen/out/font.woff").absolutePath()) ) Log.info("Done.") } } task icongen(dependsOn: classes, type: JavaExec){ mainClass = "mindustry.tools.IconConverter" classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = "../core/assets-raw" } task updateScripts(dependsOn: classes, type: JavaExec){ mainClass = "mindustry.tools.ScriptMainGenerator" classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = "../" } task updateBundles{ doLast{ def uniEscape = { String string -> StringBuilder outBuffer = new StringBuilder() int len = string.length() for(int i = 0; i < len; i++){ char ch = string.charAt(i) if((ch > 61) && (ch < 127)){ outBuffer.append(ch == (char)'\\' ? "\\\\" : ch) continue } if(ch >= 0xE000 && ch <= 0xF8FF){ String hex = Integer.toHexString((int)ch) outBuffer.append("\\u") for(int j = 0; j < 4 - hex.length(); j++){ outBuffer.append('0') } outBuffer.append(hex) }else{ outBuffer.append(ch) } } return outBuffer.toString() } OrderedMap base = new OrderedMap<>() PropertiesUtils.load(base, Fi.get("core/assets/bundles/bundle.properties").reader()) Seq removals = new Seq<>() Log.info("Updating bundles...") Fi.get("core/assets/bundles").walk(child -> { if(child.name().equals("bundle.properties") || child.toString().contains("output")) return Log.info("| @", child.nameWithoutExtension()) OrderedMap other = new OrderedMap<>() //find the last known comment of each line ObjectMap comments = new ObjectMap<>() StringBuilder curComment = new StringBuilder() for(String line : Seq.with(child.readString().split("\n", -1))){ if(line.startsWith("#") || line.isEmpty()){ curComment.append(line).append("\n") }else if(line.contains("=")){ String lastKey = line.substring(0, line.indexOf("=")).trim() if(curComment.length() != 0){ comments.put(lastKey, curComment.toString()) curComment.setLength(0) } } } ObjectMap extras = new OrderedMap<>() PropertiesUtils.load(other, child.reader()) removals.clear() for(String key : other.orderedKeys()){ if(!base.containsKey(key) && key.contains(".details") && false){ extras.put(key, other.get(key)) }else if(!base.containsKey(key)){ removals.add(key) Log.info("&lr- Removing unused key '@'...", key) } } if(removals.size > 0) Log.info("&lr@ keys removed.", removals.size) for(String s : removals){ other.remove(s) } int added = 0 for(String key : base.orderedKeys()){ if(other.get(key) == null || other.get(key).trim().isEmpty()){ other.put(key, base.get(key)) added++ Log.info("&lc- Adding missing key '@'...", key) } } Func2 processor = (key, value) -> (comments.containsKey(key) ? comments.get(key) : "") + //append last known comment if present (key + " =" + (value.trim().isEmpty() ? "" : " ") + uniEscape(value)).replace("\n", "\\n") + "\n" Fi output = child.sibling("output/" + child.name()) if(added > 0) Log.info("&lc@ keys added.", added) if(removals.size + added > 0) Log.info("Writing bundle to @", output) StringBuilder result = new StringBuilder() //add everything ordered for(String key : base.orderedKeys().copy().add(extras.keys().toSeq())){ if(other.get(key) == null) continue result.append(processor.get(key, other.get(key))) other.remove(key) } child.writeString(result.toString()) }) } }