mirror of
https://github.com/curioustorvald/Terrarum-sans-bitmap.git
synced 2026-03-15 07:26:07 +09:00
glyph texture atlas (2)
This commit is contained in:
@@ -13,7 +13,7 @@ data class AtlasRegion(
|
||||
|
||||
class GlyphAtlas(val atlasWidth: Int, val atlasHeight: Int) {
|
||||
|
||||
val pixmap = Pixmap(atlasWidth, atlasHeight, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
||||
val pixmap = Pixmap(atlasWidth, atlasHeight, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.SourceOver }
|
||||
|
||||
private val regions = HashMap<Long, AtlasRegion>()
|
||||
|
||||
@@ -21,25 +21,78 @@ class GlyphAtlas(val atlasWidth: Int, val atlasHeight: Int) {
|
||||
private var cursorY = 0
|
||||
private var shelfHeight = 0
|
||||
|
||||
private val pendingCells = ArrayList<PendingCell>()
|
||||
|
||||
private class PendingCell(
|
||||
val sheetID: Int,
|
||||
val cellX: Int,
|
||||
val cellY: Int,
|
||||
val cropped: Pixmap,
|
||||
val offsetX: Int,
|
||||
val offsetY: Int
|
||||
)
|
||||
|
||||
private fun atlasKey(sheetID: Int, cellX: Int, cellY: Int): Long =
|
||||
sheetID.toLong().shl(32) or cellX.toLong().shl(16) or cellY.toLong()
|
||||
|
||||
fun packCell(sheetID: Int, cellX: Int, cellY: Int, cellPixmap: Pixmap) {
|
||||
val w = cellPixmap.width
|
||||
val h = cellPixmap.height
|
||||
/** Scans the cell for its non-transparent bounding box, crops, and queues for deferred packing. */
|
||||
fun queueCell(sheetID: Int, cellX: Int, cellY: Int, cellPixmap: Pixmap) {
|
||||
var minX = cellPixmap.width
|
||||
var minY = cellPixmap.height
|
||||
var maxX = -1
|
||||
var maxY = -1
|
||||
|
||||
if (cursorX + w > atlasWidth) {
|
||||
cursorX = 0
|
||||
cursorY += shelfHeight
|
||||
shelfHeight = 0
|
||||
for (y in 0 until cellPixmap.height) {
|
||||
for (x in 0 until cellPixmap.width) {
|
||||
if (cellPixmap.getPixel(x, y) and 0xFF != 0) {
|
||||
if (x < minX) minX = x
|
||||
if (y < minY) minY = y
|
||||
if (x > maxX) maxX = x
|
||||
if (y > maxY) maxY = y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pixmap.drawPixmap(cellPixmap, cursorX, cursorY)
|
||||
if (maxX < 0) return // entirely transparent, skip
|
||||
|
||||
regions[atlasKey(sheetID, cellX, cellY)] = AtlasRegion(cursorX, cursorY, w, h)
|
||||
val cropW = maxX - minX + 1
|
||||
val cropH = maxY - minY + 1
|
||||
val cropped = Pixmap(cropW, cropH, Pixmap.Format.RGBA8888)
|
||||
cropped.drawPixmap(cellPixmap, 0, 0, minX, minY, cropW, cropH)
|
||||
|
||||
cursorX += w
|
||||
if (h > shelfHeight) shelfHeight = h
|
||||
pendingCells.add(PendingCell(sheetID, cellX, cellY, cropped, minX, minY))
|
||||
}
|
||||
|
||||
/** Sorts queued cells by height desc then width desc, and packs into shelves. */
|
||||
fun packAllQueued() {
|
||||
pendingCells.sortWith(
|
||||
compareByDescending<PendingCell> { it.cropped.height }
|
||||
.thenByDescending { it.cropped.width }
|
||||
)
|
||||
|
||||
for (cell in pendingCells) {
|
||||
val w = cell.cropped.width
|
||||
val h = cell.cropped.height
|
||||
|
||||
// start new shelf if cell doesn't fit horizontally
|
||||
if (cursorX + w > atlasWidth) {
|
||||
cursorX = 0
|
||||
cursorY += shelfHeight
|
||||
shelfHeight = 0
|
||||
}
|
||||
|
||||
pixmap.drawPixmap(cell.cropped, cursorX, cursorY)
|
||||
|
||||
regions[atlasKey(cell.sheetID, cell.cellX, cell.cellY)] =
|
||||
AtlasRegion(cursorX, cursorY, w, h, cell.offsetX, cell.offsetY)
|
||||
|
||||
cursorX += w
|
||||
if (h > shelfHeight) shelfHeight = h
|
||||
|
||||
cell.cropped.dispose()
|
||||
}
|
||||
|
||||
pendingCells.clear()
|
||||
}
|
||||
|
||||
fun blitSheet(sheetID: Int, sheetPixmap: Pixmap, cellW: Int, cellH: Int, cols: Int, rows: Int) {
|
||||
|
||||
@@ -386,9 +386,19 @@ class TerrarumSansBitmap(
|
||||
PixmapRegionPack(pixmap, W_LATIN_WIDE, H)
|
||||
else throw IllegalArgumentException("Unknown sheet index: $index")
|
||||
|
||||
// this code causes initial deva chars to be skipped from rendering
|
||||
// val illegalCells = HashSet<Long>()
|
||||
// for (code in codeRange[index]) {
|
||||
// if (glyphProps[code]?.isIllegal == true) {
|
||||
// val pos = getSheetwisePosition(0, code)
|
||||
// illegalCells.add(pos[0].toLong().shl(16) or pos[1].toLong())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
for (cy in 0 until texRegPack.verticalCount) {
|
||||
for (cx in 0 until texRegPack.horizontalCount) {
|
||||
atlas.packCell(index, cx, cy, texRegPack.get(cx, cy))
|
||||
// if (cx.toLong().shl(16) or cy.toLong() in illegalCells) continue
|
||||
atlas.queueCell(index, cx, cy, texRegPack.get(cx, cy))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +407,9 @@ class TerrarumSansBitmap(
|
||||
}
|
||||
}
|
||||
|
||||
// sort and pack all queued cells (tight-cropped, sorted by height then width)
|
||||
atlas.packAllQueued()
|
||||
|
||||
// pack wenquanyi (SHEET_UNIHAN) last as a contiguous blit
|
||||
unihanPixmap?.let {
|
||||
val cols = it.width / W_UNIHAN
|
||||
@@ -566,6 +579,9 @@ class TerrarumSansBitmap(
|
||||
renderCol = getColour(c)
|
||||
}
|
||||
}
|
||||
else if (isNoDrawChar(c) || glyphProps[c]?.isIllegal == true) {
|
||||
// whitespace/control/internal/invalid — no visible glyph, just advance position
|
||||
}
|
||||
else if (sheetID == SHEET_HANGUL) {
|
||||
// Flookahead for {I, P, F}
|
||||
|
||||
@@ -674,6 +690,9 @@ class TerrarumSansBitmap(
|
||||
renderCol = getColour(c)
|
||||
}
|
||||
}
|
||||
else if (isNoDrawChar(c) || glyphProps[c]?.isIllegal == true) {
|
||||
// whitespace/control/internal/invalid — no visible glyph, just advance position
|
||||
}
|
||||
else if (sheetID == SHEET_HANGUL) {
|
||||
// Flookahead for {I, P, F}
|
||||
|
||||
@@ -3008,6 +3027,13 @@ class TerrarumSansBitmap(
|
||||
private fun isBulgarian(c: CodePoint) = c in 0xF0000..0xF005F
|
||||
private fun isSerbian(c: CodePoint) = c in 0xF0060..0xF00BF
|
||||
fun isColourCode(c: CodePoint) = c == 0x100000 || c in 0x10F000..0x10FFFF
|
||||
private fun isNoDrawChar(c: CodePoint): Boolean =
|
||||
c <= 0x20 || c == NBSP || c == SHY || c == OBJ ||
|
||||
c in 0x2000..0x200D ||
|
||||
c in 0xD800..0xDFFF ||
|
||||
c in 0xF800..0xF8FF ||
|
||||
c in 0xFFF70..0xFFF9F ||
|
||||
c >= 0xFFFA0
|
||||
private fun isCharsetOverride(c: CodePoint) = c in 0xFFFC0..0xFFFCF
|
||||
private fun isDevanagari(c: CodePoint) = c in codeRange[SHEET_DEVANAGARI_VARW]
|
||||
private fun isHangulCompat(c: CodePoint) = c in codeRangeHangulCompat
|
||||
|
||||
Reference in New Issue
Block a user