Skip to content

Instantly share code, notes, and snippets.

@shirobutton
Created February 27, 2022 05:31
Show Gist options
  • Save shirobutton/accf88001baf67e6353118f863fd5296 to your computer and use it in GitHub Desktop.
Save shirobutton/accf88001baf67e6353118f863fd5296 to your computer and use it in GitHub Desktop.
BitmapToNinePatchConverter
object BitmapToNinePatchConverter {
operator fun invoke(resources: Resources, bitmap: Bitmap): NinePatchDrawable {
val trimmedBitmap = trimBitmap(bitmap)
val info = NinePatchBorderReader(bitmap).getPatchInfo()
return NinePatchDrawable(
resources,
trimmedBitmap,
info.ninePatchChunk,
info.padding,
""
)
}
private fun trimBitmap(bitmap: Bitmap) = bitmap.let {
Bitmap.createBitmap(bitmap, 1, 1, it.width - 2, it.height - 2)
}
}
private class NinePatchBorderReader(private val bitmap: Bitmap) {
private val imageWidth = bitmap.width - 2
private val imageHeight = bitmap.height - 2
fun getPatchInfo(): NinePatchInfo {
val xDividers = getOneSideDividers(imageWidth,::horizontalPatchChecker)
val yDividers = getOneSideDividers(imageHeight, ::verticalPatchChecker)
return NinePatchInfo(xDividers, yDividers, getPadding())
}
// 9-patch border starts at 1 index of bitmap
// bitmap-indexes: 0, 1, 2, 3, 4
// 9patch-indexes: 0, 1, 2
private fun horizontalPatchChecker(x: Int, y:Int = 0) = bitmap.isPatchPosition(x + 1, y)
private fun verticalPatchChecker(y: Int, x:Int = 0) = bitmap.isPatchPosition(x, y + 1)
private tailrec fun getOneSideDividers(
imageSideSize: Int,
patchChecker: (Int) -> Boolean,
beforeIsPatched: Boolean = false,
currentBorderIndex: Int = 0,
dividers: List<Int> = emptyList()
): List<Int> {
if(currentBorderIndex > imageSideSize) return dividers
val currentPositionIsPatched = patchChecker(currentBorderIndex)
val isDivider = beforeIsPatched != currentPositionIsPatched
val nextDividers = when {
isDivider -> dividers + currentBorderIndex
else -> dividers
}
return getOneSideDividers(
imageSideSize,
patchChecker,
currentPositionIsPatched,
currentBorderIndex + 1,
nextDividers
)
}
private fun getPadding(): Rect {
val (left, right) = getOneSidePaddings(imageWidth) {
horizontalPatchChecker(it, imageHeight + 1)
}
val (top, bottom) = getOneSidePaddings(imageHeight) {
verticalPatchChecker(it, imageWidth + 1)
}
return Rect(left, top, right, bottom)
}
private fun getOneSidePaddings(size: Int, checkPatch: (Int) -> Boolean) =
(0..size).let { it.first(checkPatch) to size - it.last(checkPatch) - 1 }
private fun Bitmap.isPatchPosition(x: Int, y: Int) = this.getPixel(x, y) == Color.BLACK
}
private class NinePatchInfo(
private val xDividers: List<Int>,
private val yDividers: List<Int>,
val padding: Rect
) {
val ninePatchChunk: ByteArray
get() {
val buffer = ByteBuffer.allocate(bufferCapacity).order(ByteOrder.nativeOrder())
buffer.put(WAS_SERIALISED)
.put((xDividers.size).toByte())
.put((yDividers.size).toByte())
.put(colorSize.toByte())
.position(buffer.position() + BEFORE_PADDING_SKIP_BYTE_SIZE)
buffer.putInt(padding.left)
buffer.putInt(padding.right)
buffer.putInt(padding.top)
buffer.putInt(padding.bottom)
buffer.position(buffer.position() + AFTER_PADDING_SKIP_BYTE_SIZE)
xDividers.forEach(buffer::putInt)
yDividers.forEach(buffer::putInt)
repeat(colorSize) { buffer.putInt(NO_COLOR) }
return buffer.array()
}
private val colorSize = xDividers.size * yDividers.size
private val bufferCapacity: Int
get() {
val xDividersByteSize = ONE_DIVISION_SIZE * xDividers.size
val yDividersByteSize = ONE_DIVISION_SIZE * yDividers.size
val colorByteSize = colorSize * ONE_DIVISION_SIZE
return (
HEAD_BYTE_SIZE +
BEFORE_PADDING_SKIP_BYTE_SIZE +
PADDINGS_BYTE_SIZE +
AFTER_PADDING_SKIP_BYTE_SIZE +
xDividersByteSize +
yDividersByteSize +
colorByteSize
)
}
companion object {
private const val NO_COLOR= 1
private const val WAS_SERIALISED: Byte = 1
private const val HEAD_BYTE_SIZE = 4
private const val BEFORE_PADDING_SKIP_BYTE_SIZE = 8
private const val PADDINGS_BYTE_SIZE = 16
private const val AFTER_PADDING_SKIP_BYTE_SIZE = 4
private const val ONE_DIVISION_SIZE = 4
}
}
@shirobutton
Copy link
Author

nine_patch
Screenshot_20220227_142849

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment