Skip to content

Commit

Permalink
Fix scaling and add configurable scaling
Browse files Browse the repository at this point in the history
Took 37 minutes
  • Loading branch information
Darkyenus committed Sep 3, 2017
1 parent 100138e commit a4e880b
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 41 deletions.
48 changes: 40 additions & 8 deletions src/main/java/com/darkyen/resourcepacker/image/Image.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,18 @@ val PixelSizePattern = Regex("""((?:\d+)|\?)x((?:\d+)|\?)""")
*/
val PreBlendRegex = Regex("#$ColorRegexGroup")

/**
* Matches: scaling <algo>
* Where <algo> is a name of one of the algorithms in [ImageScaling], by [ImageScaling.scalingName].
* Example:
* scaling bilinear
*/
val ScalingRegex = Regex("scaling (\\w+)")

val TileSize: SettingKey<Int> = SettingKey("TileSize", 128, "Size of tile used by w<W>h<H> flag pattern")

val DefaultImageScaling: SettingKey<ImageScaling> = 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())
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
}

}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -551,4 +583,4 @@ fun BufferedImage.saveToFile(file: File, format:String? = null) {
}
}
ImageIO.write(this, realFormat, file)
}
}
Original file line number Diff line number Diff line change
@@ -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),
}
61 changes: 28 additions & 33 deletions src/main/java/com/darkyen/resourcepacker/util/RegexUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down

0 comments on commit a4e880b

Please sign in to comment.