146 lines
4.1 KiB
Kotlin
146 lines
4.1 KiB
Kotlin
package ru.dbotthepony.kstarbound.io
|
|
|
|
import org.lwjgl.stb.STBImage
|
|
import org.lwjgl.system.MemoryUtil.memAddress
|
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
|
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITi
|
|
import ru.dbotthepony.kvector.vector.Vector2i
|
|
import ru.dbotthepony.kvector.vector.Vector4i
|
|
import java.lang.ref.Cleaner
|
|
import java.nio.ByteBuffer
|
|
|
|
private val cleaner = Cleaner.create { Thread(it, "STB Image Cleaner") }
|
|
|
|
class ImageData(val data: ByteBuffer, val width: Int, val height: Int, val amountOfChannels: Int) {
|
|
init {
|
|
val address = memAddress(data)
|
|
cleaner.register(data) { STBImage.nstbi_image_free(address) }
|
|
|
|
check(width >= 0) { "Invalid width $width" }
|
|
check(height >= 0) { "Invalid height $height" }
|
|
check(amountOfChannels in 1 .. 4) { "Unknown number of channels $amountOfChannels" }
|
|
}
|
|
|
|
operator fun get(x: Int, y: Int): Int {
|
|
require(x in 0 until width) { "X out of bounds: $x" }
|
|
require(y in 0 until height) { "Y out of bounds: $y" }
|
|
|
|
val offset = y * width * amountOfChannels + x * amountOfChannels
|
|
|
|
when (amountOfChannels) {
|
|
4 -> return data[offset].toInt() or
|
|
data[offset + 1].toInt().shl(8) or
|
|
data[offset + 2].toInt().shl(16) or
|
|
data[offset + 3].toInt().shl(24)
|
|
|
|
3 -> return data[offset].toInt() or
|
|
data[offset + 1].toInt().shl(8) or
|
|
data[offset + 2].toInt().shl(16)
|
|
|
|
2 -> return data[offset].toInt() or
|
|
data[offset + 1].toInt().shl(8)
|
|
|
|
1 -> return data[offset].toInt()
|
|
|
|
else -> throw IllegalStateException()
|
|
}
|
|
}
|
|
|
|
operator fun get(x: Int, y: Int, flip: Boolean): Int {
|
|
require(x in 0 until width) { "X out of bounds: $x" }
|
|
require(y in 0 until height) { "Y out of bounds: $y" }
|
|
|
|
if (flip) {
|
|
return this[width - x - 1, y]
|
|
} else {
|
|
return this[x, y]
|
|
}
|
|
}
|
|
|
|
fun isTransparent(x: Int, y: Int, flip: Boolean): Boolean {
|
|
if (x !in 0 until width) return true
|
|
if (y !in 0 until height) return true
|
|
if (amountOfChannels != 4) return false
|
|
return this[x, y, flip] and 0xFF != 0x0
|
|
}
|
|
|
|
fun worldSpaces(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): List<Vector2i> {
|
|
if (amountOfChannels != 3 && amountOfChannels != 4) throw IllegalStateException("Can not check world space taken by image with $amountOfChannels color channels")
|
|
|
|
val xDivL = pixelOffset.x % PIXELS_IN_STARBOUND_UNITi
|
|
val yDivB = pixelOffset.y % PIXELS_IN_STARBOUND_UNITi
|
|
|
|
val xDivR = (pixelOffset.x + width) % PIXELS_IN_STARBOUND_UNITi
|
|
val yDivT = (pixelOffset.y + height) % PIXELS_IN_STARBOUND_UNITi
|
|
|
|
val leftMostX = pixelOffset.x / PIXELS_IN_STARBOUND_UNITi - (if (xDivL != 0) 1 else 0)
|
|
val bottomMostY = pixelOffset.y / PIXELS_IN_STARBOUND_UNITi - (if (yDivB != 0) 1 else 0)
|
|
|
|
val rightMostX = (pixelOffset.x + width) / PIXELS_IN_STARBOUND_UNITi + (if (xDivR != 0) 1 else 0)
|
|
val topMostY = (pixelOffset.y + height) / PIXELS_IN_STARBOUND_UNITi + (if (yDivT != 0) 1 else 0)
|
|
|
|
val result = ArrayList<Vector2i>()
|
|
|
|
for (y in bottomMostY .. topMostY) {
|
|
for (x in leftMostX .. rightMostX) {
|
|
val left = x * PIXELS_IN_STARBOUND_UNITi
|
|
val bottom = y * PIXELS_IN_STARBOUND_UNITi
|
|
|
|
var transparentPixels = 0
|
|
|
|
for (sX in 0 until PIXELS_IN_STARBOUND_UNITi) {
|
|
for (sY in 0 until PIXELS_IN_STARBOUND_UNITi) {
|
|
if (isTransparent(xDivL + sX + left, yDivB + sY + bottom, flip)) {
|
|
transparentPixels++
|
|
}
|
|
}
|
|
}
|
|
|
|
if (transparentPixels * FILL_RATIO >= spaceScan) {
|
|
result.add(Vector2i(x, y))
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
val nonEmptyRegion by lazy {
|
|
if (amountOfChannels == 4) {
|
|
var x0 = 0
|
|
var y0 = 0
|
|
|
|
search@for (y in 0 until height) {
|
|
for (x in 0 until width) {
|
|
if (this[x, y] and 0xFF != 0x0) {
|
|
x0 = x
|
|
y0 = y
|
|
break@search
|
|
}
|
|
}
|
|
}
|
|
|
|
var x1 = x0
|
|
var y1 = y0
|
|
|
|
search@for (y in height - 1 downTo y0) {
|
|
for (x in width - 1 downTo x0) {
|
|
if (this[x, y] and 0xFF != 0x0) {
|
|
x1 = x
|
|
y1 = y
|
|
break@search
|
|
}
|
|
}
|
|
}
|
|
|
|
return@lazy Vector4i(x0, y0, x1, y1)
|
|
}
|
|
|
|
Vector4i(0, 0, width, height)
|
|
}
|
|
|
|
companion object {
|
|
const val FILL_RATIO = 1 / (PIXELS_IN_STARBOUND_UNIT * PIXELS_IN_STARBOUND_UNIT)
|
|
}
|
|
}
|