diff --git a/src/main/java/com/darkyen/resourcepacker/image/Image.kt b/src/main/java/com/darkyen/resourcepacker/image/Image.kt index f1bd56a..7d6d0fa 100644 --- a/src/main/java/com/darkyen/resourcepacker/image/Image.kt +++ b/src/main/java/com/darkyen/resourcepacker/image/Image.kt @@ -47,8 +47,18 @@ val PixelSizePattern = Regex("""((?:\d+)|\?)x((?:\d+)|\?)""") */ val PreBlendRegex = Regex("#$ColorRegexGroup") +/** + * Matches: scaling + * Where is a name of one of the algorithms in [ImageScaling], by [ImageScaling.scalingName]. + * Example: + * scaling bilinear + */ +val ScalingRegex = Regex("scaling (\\w+)") + val TileSize: SettingKey = SettingKey("TileSize", 128, "Size of tile used by wh flag pattern") +val DefaultImageScaling: SettingKey = SettingKey("DefaultImageScaling", ImageScaling.Bilinear, "Image scaling algorithm used by default") + private fun tileFraction(input: String): Int { if (input.isEmpty()) return -1 return Math.round(input.replace(',', '.').toFloat() * TileSize.get()) @@ -64,6 +74,7 @@ sealed class Image(val file:Resource.ResourceFile, private val canBeNinepatch:Bo protected var _fileWidth:Int = -1 protected var _fileHeight:Int = -1 protected var _backgroundColor:Color? = null + protected var _scaling:ImageScaling? = null protected var _ninepatch:Boolean = false protected var _ninepatchSplits:IntArray? = null protected var _ninepatchPads:IntArray? = null @@ -94,6 +105,17 @@ sealed class Image(val file:Resource.ResourceFile, private val canBeNinepatch:Bo _backgroundColor = parseHexColor(color).toAwt() } + // Scaling + file.flags.matchFirst(ScalingRegex) { (algo) -> + for (value in ImageScaling.values()) { + if (value.scalingName.equals(algo, ignoreCase = true)) { + _scaling = value + return@matchFirst + } + } + Log.warn("Image", "Unknown scaling algorithm '$algo'") + } + // Dimensions setupDimensions() if (_fileWidth < 0 || _fileHeight < 0) { @@ -215,6 +237,12 @@ sealed class Image(val file:Resource.ResourceFile, private val canBeNinepatch:Bo return _backgroundColor } + val scaling:ImageScaling + get() { + ensureImagePrepared() + return _scaling ?: DefaultImageScaling.get() + } + val ninepatch:Boolean get() { ensureImagePrepared() @@ -290,7 +318,7 @@ sealed class Image(val file:Resource.ResourceFile, private val canBeNinepatch:Bo } override fun image(width: Int, height: Int, background: Color?): BufferedImage { - return resizeImage(image(), width, height, background) + return resizeImage(image(), width, height, background, scaling) } } @@ -471,7 +499,11 @@ sealed class Image(val file:Resource.ResourceFile, private val canBeNinepatch:Bo companion object { - private fun resizeStep(current:Int, target:Int):Int { + private fun resizeStep(current:Int, target:Int, scaling: ImageScaling):Int { + if (!scaling.multiStepDownscale) { + return target + } + if (target >= current) { // When making the image bigger, single step is enough return target @@ -485,7 +517,7 @@ sealed class Image(val file:Resource.ResourceFile, private val canBeNinepatch:Bo return target } - fun resizeImage(image:BufferedImage, width: Int, height: Int, background: Color?):BufferedImage { + fun resizeImage(image:BufferedImage, width: Int, height: Int, background: Color?, scaling: ImageScaling = DefaultImageScaling.get()):BufferedImage { if (width == image.width && height == image.height && background == null) { return image @@ -498,15 +530,15 @@ sealed class Image(val file:Resource.ResourceFile, private val canBeNinepatch:Bo // Do scaling do { - val nextWidth = resizeStep(currentWidth, width) - val nextHeight = resizeStep(currentHeight, height) + val nextWidth = resizeStep(currentWidth, width, scaling) + val nextHeight = resizeStep(currentHeight, height, scaling) val lastStep = nextWidth == width && nextHeight == height val resizedImage = BufferedImage(nextWidth, nextHeight, BufferedImage.TYPE_INT_ARGB) val g = resizedImage.createGraphics() g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY) - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR) + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, scaling.awtFlag) if (lastStep && background != null) { val oldColor = g.color @@ -515,7 +547,7 @@ sealed class Image(val file:Resource.ResourceFile, private val canBeNinepatch:Bo g.color = oldColor } - g.drawImage(currentStep, 0, 0, width, height, null) + g.drawImage(currentStep, 0, 0, nextWidth, nextHeight, null) g.dispose() currentStep = resizedImage @@ -551,4 +583,4 @@ fun BufferedImage.saveToFile(file: File, format:String? = null) { } } ImageIO.write(this, realFormat, file) -} \ No newline at end of file +} diff --git a/src/main/java/com/darkyen/resourcepacker/image/ImageScaling.kt b/src/main/java/com/darkyen/resourcepacker/image/ImageScaling.kt new file mode 100644 index 0000000..145da01 --- /dev/null +++ b/src/main/java/com/darkyen/resourcepacker/image/ImageScaling.kt @@ -0,0 +1,9 @@ +package com.darkyen.resourcepacker.image + +import java.awt.RenderingHints + +enum class ImageScaling(val scalingName:String, internal val multiStepDownscale:Boolean, internal val awtFlag:Any) { + Nearest("nearest", false, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR), + Bilinear("bilinear", true, RenderingHints.VALUE_INTERPOLATION_BILINEAR), + Bicubic("bicubic", false, RenderingHints.VALUE_INTERPOLATION_BICUBIC), +} \ No newline at end of file diff --git a/src/main/java/com/darkyen/resourcepacker/util/RegexUtil.kt b/src/main/java/com/darkyen/resourcepacker/util/RegexUtil.kt index f75e966..880f35b 100644 --- a/src/main/java/com/darkyen/resourcepacker/util/RegexUtil.kt +++ b/src/main/java/com/darkyen/resourcepacker/util/RegexUtil.kt @@ -69,64 +69,59 @@ internal val ColorRegexGroup = "([0-9A-Fa-f]{1,8})" * Parse color matched by [ColorRegexGroup]. */ internal fun parseHexColor(hex: String): Color { - fun Int.c(): Float = MathUtils.clamp(this, 0, 255).toFloat() / 255f + + fun Int.F(): Float = MathUtils.clamp(this, 0, 0xF).toFloat() / 0xF.toFloat() + fun Int.FF(): Float = MathUtils.clamp(this, 0, 0xFF).toFloat() / 0xFF.toFloat() return when (hex.length) { 1 -> {//Gray - val gray = (Integer.parseInt(hex, 16) * 0xF).c() + val gray = Integer.parseInt(hex, 16).F() Color(gray, gray, gray, 1f) } 2 -> { //Gray with alpha - val gray = (Integer.parseInt(hex.substring(0, 1), 16) * 0xF).c() - val alpha = (Integer.parseInt(hex.substring(1, 2), 16) * 0xF).c() + val gray = Integer.parseInt(hex.substring(0, 1), 16).F() + val alpha = Integer.parseInt(hex.substring(1, 2), 16).F() Color(gray, gray, gray, alpha) - } 3 -> {//RGB - val r = (Integer.parseInt(hex.substring(0, 1), 16) * 0xF).c() - val g = (Integer.parseInt(hex.substring(1, 2), 16) * 0xF).c() - val b = (Integer.parseInt(hex.substring(2, 3), 16) * 0xF).c() + val r = Integer.parseInt(hex.substring(0, 1), 16).F() + val g = Integer.parseInt(hex.substring(1, 2), 16).F() + val b = Integer.parseInt(hex.substring(2, 3), 16).F() Color(r, g, b, 1f) - } 4 -> {//RGBA - val r = (Integer.parseInt(hex.substring(0, 1), 16) * 0xF).c() - val g = (Integer.parseInt(hex.substring(1, 2), 16) * 0xF).c() - val b = (Integer.parseInt(hex.substring(2, 3), 16) * 0xF).c() - val a = (Integer.parseInt(hex.substring(3, 4), 16) * 0xF).c() + val r = Integer.parseInt(hex.substring(0, 1), 16).F() + val g = Integer.parseInt(hex.substring(1, 2), 16).F() + val b = Integer.parseInt(hex.substring(2, 3), 16).F() + val a = Integer.parseInt(hex.substring(3, 4), 16).F() Color(r, g, b, a) - } 5 -> {//RGBAA - val r = (Integer.parseInt(hex.substring(0, 1), 16) * 0xF).c() - val g = (Integer.parseInt(hex.substring(1, 2), 16) * 0xF).c() - val b = (Integer.parseInt(hex.substring(2, 3), 16) * 0xF).c() - val a = Integer.parseInt(hex.substring(3, 5), 16).c() + val r = Integer.parseInt(hex.substring(0, 1), 16).F() + val g = Integer.parseInt(hex.substring(1, 2), 16).F() + val b = Integer.parseInt(hex.substring(2, 3), 16).F() + val a = Integer.parseInt(hex.substring(3, 5), 16).FF() Color(r, g, b, a) - } 6 -> {//RRGGBB - val r = Integer.parseInt(hex.substring(0, 2), 16).c() - val g = Integer.parseInt(hex.substring(2, 4), 16).c() - val b = Integer.parseInt(hex.substring(4, 6), 16).c() + val r = Integer.parseInt(hex.substring(0, 2), 16).FF() + val g = Integer.parseInt(hex.substring(2, 4), 16).FF() + val b = Integer.parseInt(hex.substring(4, 6), 16).FF() Color(r, g, b, 1f) - } 7 -> {//RRGGBBA - val r = Integer.parseInt(hex.substring(0, 2), 16).c() - val g = Integer.parseInt(hex.substring(2, 4), 16).c() - val b = Integer.parseInt(hex.substring(4, 6), 16).c() - val a = (Integer.parseInt(hex.substring(6, 7), 16) * 0xF).c() + val r = Integer.parseInt(hex.substring(0, 2), 16).FF() + val g = Integer.parseInt(hex.substring(2, 4), 16).FF() + val b = Integer.parseInt(hex.substring(4, 6), 16).FF() + val a = Integer.parseInt(hex.substring(6, 7), 16).F() Color(r, g, b, a) - } 8 -> {//RRGGBBAA - val r = Integer.parseInt(hex.substring(0, 2), 16).c() - val g = Integer.parseInt(hex.substring(2, 4), 16).c() - val b = Integer.parseInt(hex.substring(4, 6), 16).c() - val a = Integer.parseInt(hex.substring(6, 8), 16).c() + val r = Integer.parseInt(hex.substring(0, 2), 16).FF() + val g = Integer.parseInt(hex.substring(2, 4), 16).FF() + val b = Integer.parseInt(hex.substring(4, 6), 16).FF() + val a = Integer.parseInt(hex.substring(6, 8), 16).FF() Color(r, g, b, a) - } else -> error("Invalid color: $hex") }