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.IntMap 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) } tasks.register('antialiasImages'){ doLast{ for(def img : project.getProperty("images").split(",")){ println(project.getProperty("startdir") + "/" + img) antialias(new File(project.getProperty("startdir") + "/" + img)) } } } tasks.register('tileImages'){ doLast{ for(def img : project.getProperty("images").split(",")){ println(project.getProperty("startdir") + "/" + img) tileImage(new File(project.getProperty("startdir") + "/" + img)) } } } tasks.register('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() project.services.get(ExecOperations).javaexec{ mainClass = "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") } } } tasks.register('genSprites', JavaExec){ dependsOn classes finalizedBy 'antialiasGen' mainClass = "mindustry.tools.ImagePacker" classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = genFolder } tasks.register('fontgen', JavaExec){ dependsOn classes /* 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.") } } tasks.register('icongen', JavaExec){ dependsOn classes mainClass = "mindustry.tools.IconConverter" classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = "../core/assets-raw" } tasks.register('updateScripts', JavaExec){ dependsOn classes mainClass = "mindustry.tools.ScriptMainGenerator" classpath = sourceSets.main.runtimeClasspath standardInput = System.in workingDir = "../" } tasks.register('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()) //map of line number to comment (or just empty string for blank line) content IntMap commentAndBlankLines = new IntMap<>() var lines = Fi.get("core/assets/bundles/bundle.properties").reader().readLines() int offset = 0 for(int i = 0; i < lines.size(); i++){ var line = lines.get(i) if(i > 0 && lines.get(i - 1).endsWith("\\")) offset++ //multiline value escaped with \\ at end of line else if(line.isEmpty() || line.startsWith("#")) commentAndBlankLines.put(i - offset, line) } Log.info("Updating bundles...") Fi.get("core/assets/bundles").walk(child -> { if(child.name() == "bundle.properties" || child.name() == "global.properties" || child.toString().contains("output")) return if(project.hasProperty("bundle") && child.name() != project.property("bundle")) return Log.info("| @", child.nameWithoutExtension()) OrderedMap other = new OrderedMap<>() ObjectMap extras = new OrderedMap<>() Seq removals = new Seq<>() PropertiesUtils.load(other, child.reader()) 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) -> (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() int i = 0 //add everything ordered for(String key : base.orderedKeys().copy().add(extras.keys().toSeq())){ //append any comments or blank lines as needed to match the english bundle while(commentAndBlankLines.containsKey(i++)) result.append(commentAndBlankLines.get(i - 1) + "\n") if(other.get(key) == null) continue result.append(processor.get(key, other.get(key))) other.remove(key) } child.writeString(result.toString()) }) } }