From 9c3e621b158aa7ffa9395bb73e899eb64d481e6d Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Sun, 29 Aug 2021 22:29:24 +0200 Subject: [PATCH] Atlas reorg - packer to loader via json (#5014) --- .../Skin/checkbox-pressed.png | Bin android/{ => Images.Skin}/Skin/checkbox.png | Bin .../Skin/rectangleWithOutline.png | Bin .../Skin/roundedEdgeRectangle.png | Bin .../Skin/select-box-pressed.png | Bin android/{ => Images.Skin}/Skin/select-box.png | Bin android/Images.Skin/TexturePacker.settings | 12 ++++ android/assets/Atlases.json | 1 + android/assets/Skin.atlas | 12 ++-- core/src/com/unciv/ui/utils/ImageGetter.kt | 12 ++-- .../src/com/unciv/app/desktop/ImagePacker.kt | 61 +++++++++++------- 11 files changed, 62 insertions(+), 36 deletions(-) rename android/{ => Images.Skin}/Skin/checkbox-pressed.png (100%) rename android/{ => Images.Skin}/Skin/checkbox.png (100%) rename android/{ => Images.Skin}/Skin/rectangleWithOutline.png (100%) rename android/{ => Images.Skin}/Skin/roundedEdgeRectangle.png (100%) rename android/{ => Images.Skin}/Skin/select-box-pressed.png (100%) rename android/{ => Images.Skin}/Skin/select-box.png (100%) create mode 100644 android/Images.Skin/TexturePacker.settings create mode 100644 android/assets/Atlases.json diff --git a/android/Skin/checkbox-pressed.png b/android/Images.Skin/Skin/checkbox-pressed.png similarity index 100% rename from android/Skin/checkbox-pressed.png rename to android/Images.Skin/Skin/checkbox-pressed.png diff --git a/android/Skin/checkbox.png b/android/Images.Skin/Skin/checkbox.png similarity index 100% rename from android/Skin/checkbox.png rename to android/Images.Skin/Skin/checkbox.png diff --git a/android/Skin/rectangleWithOutline.png b/android/Images.Skin/Skin/rectangleWithOutline.png similarity index 100% rename from android/Skin/rectangleWithOutline.png rename to android/Images.Skin/Skin/rectangleWithOutline.png diff --git a/android/Skin/roundedEdgeRectangle.png b/android/Images.Skin/Skin/roundedEdgeRectangle.png similarity index 100% rename from android/Skin/roundedEdgeRectangle.png rename to android/Images.Skin/Skin/roundedEdgeRectangle.png diff --git a/android/Skin/select-box-pressed.png b/android/Images.Skin/Skin/select-box-pressed.png similarity index 100% rename from android/Skin/select-box-pressed.png rename to android/Images.Skin/Skin/select-box-pressed.png diff --git a/android/Skin/select-box.png b/android/Images.Skin/Skin/select-box.png similarity index 100% rename from android/Skin/select-box.png rename to android/Images.Skin/Skin/select-box.png diff --git a/android/Images.Skin/TexturePacker.settings b/android/Images.Skin/TexturePacker.settings new file mode 100644 index 0000000000..7903f4d237 --- /dev/null +++ b/android/Images.Skin/TexturePacker.settings @@ -0,0 +1,12 @@ +{ + "paddingX":8, + "paddingY":8, + "duplicatePadding":true, + "maxWidth":2048, + "maxHeight":2048, + "filterMin":"Linear", + "filterMag":"Linear", + "fast":false, + "limitMemory":false, + "combineSubdirectories":true +} diff --git a/android/assets/Atlases.json b/android/assets/Atlases.json new file mode 100644 index 0000000000..c8cbce3725 --- /dev/null +++ b/android/assets/Atlases.json @@ -0,0 +1 @@ +[Construction,Tech,Skin,Flags] \ No newline at end of file diff --git a/android/assets/Skin.atlas b/android/assets/Skin.atlas index 1982688c9d..6d1720d19f 100644 --- a/android/assets/Skin.atlas +++ b/android/assets/Skin.atlas @@ -4,42 +4,42 @@ size: 256, 64 format: RGBA8888 filter: Linear, Linear repeat: none -checkbox +Skin/checkbox rotate: false xy: 160, 23 size: 31, 31 orig: 31, 31 offset: 0, 0 index: -1 -checkbox-pressed +Skin/checkbox-pressed rotate: false xy: 199, 23 size: 31, 31 orig: 31, 31 offset: 0, 0 index: -1 -rectangleWithOutline +Skin/rectangleWithOutline rotate: false xy: 64, 13 size: 3, 3 orig: 3, 3 offset: 0, 0 index: -1 -roundedEdgeRectangle +Skin/roundedEdgeRectangle rotate: false xy: 4, 4 size: 52, 50 orig: 52, 50 offset: 0, 0 index: -1 -select-box +Skin/select-box rotate: false xy: 64, 24 size: 40, 30 orig: 40, 30 offset: 0, 0 index: -1 -select-box-pressed +Skin/select-box-pressed rotate: false xy: 112, 24 size: 40, 30 diff --git a/core/src/com/unciv/ui/utils/ImageGetter.kt b/core/src/com/unciv/ui/utils/ImageGetter.kt index 50fcffcd44..bc74bb5cd7 100644 --- a/core/src/com/unciv/ui/utils/ImageGetter.kt +++ b/core/src/com/unciv/ui/utils/ImageGetter.kt @@ -17,6 +17,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.UncivGame +import com.unciv.logic.GameSaver import com.unciv.models.ruleset.Era import com.unciv.models.ruleset.Nation import com.unciv.models.ruleset.Ruleset @@ -42,7 +43,7 @@ object ImageGetter { // We then shove all the drawables into a hashmap, because the atlas specifically tells us // that the search on it is inefficient - internal val textureRegionDrawables = HashMap() + private val textureRegionDrawables = HashMap() fun resetAtlases() { atlases.values.forEach { it.dispose() } @@ -61,8 +62,8 @@ object ImageGetter { textureRegionDrawables[region.name] = drawable } - // This is a quickfix for #4993, since you can't .list() on a jar file. This should be fixed in a nicer way. - val fileNames = listOf("game","Flags","Tech","Skin","Construction") + // See #4993 - you can't .list() on a jar file, so the ImagePacker leaves us the list of actual atlases. + val fileNames = GameSaver.json().fromJson(Array::class.java, Gdx.files.internal("Atlases.json")) for (fileName in fileNames) { val file = Gdx.files.internal("$fileName.atlas") val extraAtlas = file.nameWithoutExtension() @@ -70,10 +71,9 @@ object ImageGetter { ?: TextureAtlas(file.name()).apply { // load if not atlases[extraAtlas] = this // cache the freshly loaded } - val prefix = if (extraAtlas == "Skin") "Skin/" else "" // Only Skin is packed without folder prefix for (region in tempAtlas.regions) { val drawable = TextureRegionDrawable(region) - textureRegionDrawables[prefix + region.name] = drawable + textureRegionDrawables[region.name] = drawable } } @@ -114,7 +114,7 @@ object ImageGetter { * getLayeredImageColored("TileSets/FantasyHex/Units/Warrior", null, Color.GOLD, Color.RED) * * All images in the atlas that match the pattern "TileSets/FantasyHex/Units/Warrior" or - * "TileSets/FantasyHex/Units/Warrior-NUMBER" are retrieved. NUMBERs must start from 1 and + * "TileSets/FantasyHex/Units/Warrior-NUMBER" are retrieved. NUMBER must start from 1 and * be incremented by 1 per layer. If the n-th NUMBER is missing, the (n-1)-th layer is the * last one retrieved: * Given the layer names: diff --git a/desktop/src/com/unciv/app/desktop/ImagePacker.kt b/desktop/src/com/unciv/app/desktop/ImagePacker.kt index 27fedde78b..aa20ff7019 100644 --- a/desktop/src/com/unciv/app/desktop/ImagePacker.kt +++ b/desktop/src/com/unciv/app/desktop/ImagePacker.kt @@ -2,9 +2,12 @@ package com.unciv.app.desktop import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.tools.texturepacker.TexturePacker +import com.badlogic.gdx.utils.Json import java.io.File /** + * Entry point: _ImagePacker.[packImages] ()_ + * * Re-packs our texture assets into atlas + png File pairs, which will be loaded by the game. * With the exception of the ExtraImages folder and the Font system these are the only * graphics used (The source Image folders are unused at run time except here). @@ -12,11 +15,7 @@ import java.io.File * [TexturePacker] documentation is [here](https://github.com/libgdx/libgdx/wiki/Texture-packer) */ internal object ImagePacker { - - fun packImages() { - val startTime = System.currentTimeMillis() - - val settings = TexturePacker.Settings() + private fun getDefaultSettings() = TexturePacker.Settings().apply { // Apparently some chipsets, like NVIDIA Tegra 3 graphics chipset (used in Asus TF700T tablet), // don't support non-power-of-two texture sizes - kudos @yuroller! // https://github.com/yairm210/UnCiv/issues/1340 @@ -35,33 +34,38 @@ internal object ImagePacker { * * TL;DR this should be 2048. */ - settings.maxWidth = 2048 - settings.maxHeight = 2048 + maxWidth = 2048 + maxHeight = 2048 // Trying to disable the subdirectory combine lead to even worse results. Don't. - settings.combineSubdirectories = true - settings.pot = true // powers of two only for width/height - settings.fast = true // with pot on this just resorts by width + combineSubdirectories = true + pot = true // powers of two only for width/height + fast = true // with pot on this just sorts by width // settings.rotation - do not set. Allows rotation, potentially packing tighter. // Proper rendering is mostly automatic - except borders which overwrite rotation. // Set some additional padding and enable duplicatePadding to prevent image edges from bleeding into each other due to mipmapping - settings.paddingX = 8 - settings.paddingY = 8 - settings.duplicatePadding = true - settings.filterMin = Texture.TextureFilter.MipMapLinearLinear - settings.filterMag = Texture.TextureFilter.MipMapLinearLinear // I'm pretty sure this doesn't make sense for magnification, but setting it to Linear gives strange results + paddingX = 8 + paddingY = 8 + duplicatePadding = true + filterMin = Texture.TextureFilter.MipMapLinearLinear + filterMag = Texture.TextureFilter.MipMapLinearLinear // I'm pretty sure this doesn't make sense for magnification, but setting it to Linear gives strange results + } + fun packImages() { + val startTime = System.currentTimeMillis() + + val defaultSettings = getDefaultSettings() + + // Scan for Image folders and build one atlas each if (File("../Images").exists()) { // So we don't run this from within a fat JAR + val atlasList = mutableListOf() for ((file, packFileName) in imageFolders()) { - packImagesIfOutdated(settings, file, ".", packFileName) + atlasList += packFileName + packImagesIfOutdated(defaultSettings, file, ".", packFileName) } - } - - if (File("../Skin").exists()) { - settings.filterMag = Texture.TextureFilter.Linear - settings.filterMin = Texture.TextureFilter.Linear - packImagesIfOutdated(settings, "../Skin", ".", "Skin") + atlasList.remove("game") + File("Atlases.json").writeText(atlasList.joinToString(",","[","]")) } // pack for mods as well @@ -69,7 +73,7 @@ internal object ImagePacker { if (modDirectory.exists()) { for (mod in modDirectory.listFiles()!!) { if (!mod.isHidden && File(mod.path + "/Images").exists()) - packImagesIfOutdated(settings, mod.path + "/Images", mod.path, "game") + packImagesIfOutdated(defaultSettings, mod.path + "/Images", mod.path, "game") } } @@ -77,22 +81,31 @@ internal object ImagePacker { println("Packing textures - " + texturePackingTime + "ms") } - private fun packImagesIfOutdated(settings: TexturePacker.Settings, input: String, output: String, packFileName: String) { + // Process one Image folder, checking for atlas older than contained images first + private fun packImagesIfOutdated(defaultSettings: TexturePacker.Settings, input: String, output: String, packFileName: String) { fun File.listTree(): Sequence = when { this.isFile -> sequenceOf(this) this.isDirectory -> this.listFiles()!!.asSequence().flatMap { it.listTree() } else -> sequenceOf() } + // Check if outdated val atlasFile = File("$output${File.separator}$packFileName.atlas") if (atlasFile.exists() && File("$output${File.separator}$packFileName.png").exists()) { val atlasModTime = atlasFile.lastModified() if (File(input).listTree().none { it.extension in listOf("png", "jpg", "jpeg") && it.lastModified() > atlasModTime }) return } + // An image folder can optionally have a TexturePacker settings file + val settingsFile = File("$input${File.separator}TexturePacker.settings") + val settings = if (settingsFile.exists()) + Json().fromJson(TexturePacker.Settings::class.java, settingsFile.reader()) + else defaultSettings + TexturePacker.process(settings, input, output, packFileName) } + // Iterator providing all Image folders to process with the destination atlas name private data class ImageFolderResult(val folder: String, val atlasName: String) private fun imageFolders() = sequence { val parent = File("..")