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 { 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() 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) } }