wip bitmap font loading + tests

This commit is contained in:
Bixilon 2023-06-16 01:18:18 +02:00
parent ce3a678d39
commit 1a6b8d04a7
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
6 changed files with 166 additions and 10 deletions

View File

@ -0,0 +1,75 @@
package de.bixilon.minosoft.gui.rendering.font.types.bitmap
import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
import de.bixilon.minosoft.gui.rendering.font.types.empty.EmptyCodeRenderer
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
import de.bixilon.minosoft.gui.rendering.system.dummy.texture.DummyTexture
import org.testng.Assert.*
import org.testng.annotations.Test
import java.nio.ByteBuffer
import java.util.stream.IntStream
import kotlin.reflect.full.companionObject
@Test(groups = ["font"])
class BitmapFontTypeTest {
private val LOAD = BitmapFontType::class.companionObject!!.java.getDeclaredMethod("load", AbstractTexture::class.java, Int::class.java, Int::class.java, Array<IntStream>::class.java).apply { isAccessible = true }
private fun createTexture(start: IntArray, end: IntArray, width: Int, height: Int): AbstractTexture {
check(start.size == end.size)
val buffer = ByteBuffer.allocate((start.size / 16 + 1) * 16 * width * height * 4)
for (row in 0..start.size / 16) {
for (char in 0 until 16) {
val start = start.getOrNull((row * 16) + char) ?: 0
val end = end.getOrNull((row * 16) + char) ?: width
for (pixel in 0 until width) {
buffer.put(0xFF.toByte())
buffer.put(0xFF.toByte())
buffer.put(0xFF.toByte())
if (pixel in start..end) {
buffer.put(0xFF.toByte())
} else {
buffer.put(0x00.toByte())
}
}
}
}
val texture = DummyTexture(minosoft("test"))
texture.data = buffer
return texture
}
private fun load(start: IntArray, end: IntArray, width: Int = 8, height: Int = 8, ascent: Int = 8, chars: Array<IntArray>): BitmapFontType {
val texture = createTexture(start, end, width, height)
val fontType = LOAD(BitmapFontType, texture, height, ascent, chars.map { IntStream.of(*it) }.toTypedArray()) as BitmapFontType
return fontType
}
fun `space size`() {
val font = load(intArrayOf(), intArrayOf(), chars = arrayOf(intArrayOf('a'.code)))
val a = font['a'.code]!! as EmptyCodeRenderer
assertEquals(a.width, 4)
}
fun `load basic with default options`() {
val font = load(intArrayOf(1, 2, 3), intArrayOf(7, 4, 6), chars = arrayOf(intArrayOf('a'.code, 'b'.code, 'c'.code)))
val a = font['a'.code]!! as BitmapCodeRenderer
assertEquals(a.width, 7)
assertEquals(a.uvStart, Vec2(0.0078125, 0))
assertEquals(a.uvEnd, Vec2(0.0625, 0))
// TODO
}
}

View File

@ -35,9 +35,7 @@ class DummyTexture(
override val transparency: TextureTransparencies get() = TextureTransparencies.OPAQUE
override var properties: ImageProperties = ImageProperties()
override var renderData: TextureRenderData = DummyTextureRenderData
override var data: ByteBuffer?
get() = TODO("Not yet implemented")
set(value) {}
override var data: ByteBuffer? = null
override var mipmapData: Array<ByteBuffer>?
get() = TODO("Not yet implemented")
set(value) {}

View File

@ -17,5 +17,5 @@ import de.bixilon.kutil.latch.AbstractLatch
interface PostInitFontType : FontType {
fun postInit(latch: AbstractLatch) = Unit
fun postInit(latch: AbstractLatch)
}

View File

@ -23,6 +23,7 @@ class BitmapCodeRenderer(
override var uvStart: Vec2,
override var uvEnd: Vec2,
override val width: Float,
val ascent1: Int,
) : RasterizedCodePointRenderer, AscentedCodePointRenderer {
override val ascent: Float
get() = 1.0f

View File

@ -13,31 +13,47 @@
package de.bixilon.minosoft.gui.rendering.font.types.bitmap
import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.kutil.json.JsonObject
import de.bixilon.kutil.latch.AbstractLatch
import de.bixilon.kutil.primitive.IntUtil.toInt
import de.bixilon.minosoft.data.registries.identified.Namespaces.minecraft
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderContext
import de.bixilon.minosoft.gui.rendering.font.types.FontType
import de.bixilon.minosoft.gui.rendering.font.renderer.code.CodePointRenderer
import de.bixilon.minosoft.gui.rendering.font.types.PostInitFontType
import de.bixilon.minosoft.gui.rendering.font.types.empty.EmptyCodeRenderer
import de.bixilon.minosoft.gui.rendering.font.types.factory.FontTypeFactory
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.texture
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import de.bixilon.minosoft.util.nbt.tag.NBTUtil.listCast
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import java.nio.ByteBuffer
import java.util.stream.IntStream
class BitmapFontType(
val chars: Int2ObjectOpenHashMap<BitmapCodeRenderer>,
) : FontType {
val chars: Int2ObjectOpenHashMap<CodePointRenderer>,
) : PostInitFontType {
init {
chars.trim()
}
override fun get(codePoint: Int): BitmapCodeRenderer {
override fun get(codePoint: Int): CodePointRenderer? {
return chars[codePoint]
}
override fun postInit(latch: AbstractLatch) {
for (char in chars.values) {
if (char !is BitmapCodeRenderer) continue
char.updateArray()
}
}
companion object : FontTypeFactory<BitmapFontType> {
private const val ROW = 16
override val identifier = minecraft("bitmap")
override fun build(context: RenderContext, data: JsonObject): BitmapFontType? {
@ -48,12 +64,78 @@ class BitmapFontType(
return load(file, height, ascent, chars, context)
}
private fun List<String>.codePoints(): Array<IntStream> {
return this.map { it.codePoints() }.toTypedArray()
}
private fun load(file: ResourceLocation, height: Int, ascent: Int, chars: List<String>, context: RenderContext): BitmapFontType? {
if (chars.isEmpty() || height <= 0) return null
val texture = context.textureManager.staticTextures.createTexture(file)
texture.load(context.connection.assetsManager) // force load it, we need to calculate the width of every char
return load(texture, height, ascent, chars.codePoints())
}
private fun ByteBuffer.scanLine(y: Int, width: Int, start: IntArray, end: IntArray) {
for (index in 0 until (width * ROW)) {
val alpha = this[(((ROW * width) * y) + index) * 4 + 3].toInt()
if (alpha == 0) {
// transparent
continue
}
val char = index / width
val pixel = index % width
start[char] = minOf(start[char], pixel)
end[char] = maxOf(end[char], pixel)
}
}
private fun createRenderer(texture: AbstractTexture, row: Int, column: Int, start: Int, end: Int, height: Int, ascent: Int): CodePointRenderer {
if (end < start) return EmptyCodeRenderer()
val uvStart = Vec2()
val uvEnd = Vec2(0.1f)
val width = 1.0f
// TODO
return null
return BitmapCodeRenderer(texture, uvStart, uvEnd, width, ascent)
}
private fun load(texture: AbstractTexture, height: Int, ascent: Int, chars: Array<IntStream>): BitmapFontType? {
val rows = chars.size
val width = texture.size.x / ROW
val start = IntArray(ROW) { width }
val end = IntArray(ROW)
val renderer = Int2ObjectOpenHashMap<CodePointRenderer>()
for (row in 0 until rows) {
val iterator = chars[row].iterator()
for (pixel in 0 until height) {
texture.data!!.scanLine(row + pixel, width, start, end)
}
var column = 0
while (iterator.hasNext()) {
val codePoint = iterator.nextInt()
renderer[codePoint] = createRenderer(texture, row, column, start[column], end[column], height, ascent)
column++
}
start.fill(width); end.fill(0) // fill with maximum values again
}
texture.data!!.rewind()
if (renderer.isEmpty()) return null
return BitmapFontType(renderer)
}
}
}

View File

@ -22,7 +22,7 @@ import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
data class EmptyCodeRenderer(
val width: Int,
val width: Int = 4,
) : CodePointRenderer {
init {