changeset 873:1ed09c1bc79c

add: packed textures for font-atlas
author Sam <sam@basx.dev>
date Sat, 20 Jan 2024 21:28:17 +0700
parents 1ee397815b0b
children a91219ef6ef9
files semicongine/core/imagetypes.nim semicongine/resources/font.nim tests/test_font.nim
diffstat 3 files changed, 29 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/core/imagetypes.nim	Sat Jan 20 20:17:03 2024 +0700
+++ b/semicongine/core/imagetypes.nim	Sat Jan 20 21:28:17 2024 +0700
@@ -59,12 +59,12 @@
 
   image[].imagedata[y * image.width + x] = value
 
-proc newImage*[T: Pixel](width, height: int, imagedata: seq[T]= @[]): Image[T] =
+proc newImage*[T: Pixel](width, height: int, imagedata: openArray[T]= []): Image[T] =
   assert width > 0 and height > 0
   assert imagedata.len == width * height or imagedata.len == 0
 
   result = new Image[T]
-  result.imagedata = (if imagedata.len == 0: newSeq[T](width * height) else: imagedata)
+  result.imagedata = (if imagedata.len == 0: newSeq[T](width * height) else: @imagedata)
   assert width * height == result.imagedata.len
 
   result.width = width
--- a/semicongine/resources/font.nim	Sat Jan 20 20:17:03 2024 +0700
+++ b/semicongine/resources/font.nim	Sat Jan 20 21:28:17 2024 +0700
@@ -1,6 +1,7 @@
 import times
 import std/tables
 import std/strformat
+import std/sequtils
 import std/streams
 import std/os
 import std/unicode
@@ -39,6 +40,7 @@
   if stbtt_InitFont(addr fontinfo, addr indata[0], 0) == 0:
     raise newException(Exception, "An error occured while loading PNG file")
 
+  result.name = name
   result.fontscale = float32(stbtt_ScaleForPixelHeight(addr fontinfo, cfloat(lineHeightPixels)))
 
   # ensure all codepoints are available in the font
@@ -47,10 +49,11 @@
       warn &"Loading font {name}: Codepoint '{codePoint}' ({cint(codePoint)}) has no glyph"
 
   var
-    offsetX = 0
     bitmaps: Table[Rune, (cstring, cint, cint)]
     topOffsets: Table[Rune, int]
     images: seq[Image[GrayPixel]]
+  let empty_image = newImage[GrayPixel](1, 1, [0'u8])
+
   for codePoint in codePoints:
     var
       width, height: cint
@@ -64,66 +67,54 @@
         addr width, addr height,
         addr offX, addr offY
       )
+    topOffsets[codePoint] = offY
 
     if width > 0 and height > 0:
       var bitmap = newSeq[GrayPixel](width * height)
       for i in 0 ..< width * height:
         bitmap[i] = GrayPixel(data[i])
       images.add newImage[GrayPixel](int(width), int(height), bitmap)
+    else:
+      images.add empty_image
 
-    bitmaps[codePoint] = (data, width, height)
-    result.maxHeight = max(result.maxHeight, int(height))
-    offsetX += width
-    topOffsets[codePoint] = offY
-  assert offsetX < MAX_TEXTURE_WIDTH, &"Font size too big, choose a smaller lineHeightPixels when loading the font (required texture width is {offsetX} but max is {MAX_TEXTURE_WIDTH}), must be smaller than {lineHeightPixels * float(MAX_TEXTURE_WIDTH) / float(offsetX) } (approx.)"
+
+    free(data)
 
   let packed = pack(images)
-  packed.atlas.writePNG("tmp.png")
 
-  result.name = name
   result.fontAtlas = Texture(
     name: name & "_texture",
     isGrayscale: true,
-    grayImage: newImage[GrayPixel](offsetX, result.maxHeight),
-    sampler: FONTSAMPLER_SOFT
+    grayImage: packed.atlas,
+    sampler: FONTSAMPLER_SOFT,
   )
 
-  for codePoint in codePoints:
-    let
-      bitmap = bitmaps[codePoint][0]
-      width = bitmaps[codePoint][1]
-      height = bitmaps[codePoint][2]
-
-  offsetX = 0
-  for codePoint in codePoints:
+  let w = float32(packed.atlas.width)
+  let h = float32(packed.atlas.height)
+  for i in 0 ..< codePoints.len:
     let
-      bitmap = bitmaps[codePoint][0]
-      width = bitmaps[codePoint][1]
-      height = bitmaps[codePoint][2]
-
-    # bitmap data
-    for y in 0 ..< height:
-      for x in 0 ..< width:
-        result.fontAtlas.grayImage[x + offsetX, y] = uint8(bitmap[y * width + x])
-
+      codePoint = codePoints[i]
+      image = images[i]
+      coord = (x: float32(packed.coords[i].x), y: float32(packed.coords[i].y))
+      iw = float32(image.width)
+      ih = float32(image.height)
     # horizontal spaces:
     var advance, leftBearing: cint
     stbtt_GetCodepointHMetrics(addr fontinfo, cint(codePoint), addr advance, addr leftBearing)
 
     result.glyphs[codePoint] = GlyphInfo(
-      dimension: newVec2f(float32(width), float32(height)),
+      dimension: newVec2f(float32(image.width), float32(image.height)),
       uvs: [
-        newVec2f((float32(offsetX) + 0.5) / float32(result.fontAtlas.grayImage.width), (float32(height) - 1.0) / float32(result.maxHeight)),
-        newVec2f((float32(offsetX) + 0.5) / float32(result.fontAtlas.grayImage.width), 0.5 / float32(result.maxHeight)),
-        newVec2f((float32(offsetX + width) - 1.0) / float32(result.fontAtlas.grayImage.width), 0.5 / float32(result.maxHeight)),
-        newVec2f((float32(offsetX + width) - 1.0) / float32(result.fontAtlas.grayImage.width), (float32(height) - 1.0) / float32(result.maxHeight)),
+        newVec2f((coord.x + 0.5 )     / w, (coord.y + ih - 0.5) / h),
+        newVec2f((coord.x + 0.5 )     / w, (coord.y + 0.5)      / h),
+        newVec2f((coord.x + iw - 0.5) / w, (coord.y + 0.5)      / h),
+        newVec2f((coord.x + iw - 0.5) / w, (coord.y + ih - 0.5) / h),
       ],
       topOffset: float32(topOffsets[codePoint]),
       leftOffset: float32(leftBearing) * result.fontscale,
       advance: float32(advance) * result.fontscale,
     )
-    offsetX += width
-    free(bitmap)
+
     for codePointAfter in codePoints:
       result.kerning[(codePoint, codePointAfter)] = float32(stbtt_GetCodepointKernAdvance(
         addr fontinfo,
--- a/tests/test_font.nim	Sat Jan 20 20:17:03 2024 +0700
+++ b/tests/test_font.nim	Sat Jan 20 21:28:17 2024 +0700
@@ -10,9 +10,9 @@
   # build scene
   var scene = Scene(name: "main")
   # var font = loadFont("DejaVuSans.ttf", lineHeightPixels=90'f32, charset="abcdefghijklmnopqrstuvwxyz ".toRunes)
-  var font = loadFont("DejaVuSans.ttf", lineHeightPixels=90'f32)
+  var font = loadFont("DejaVuSans.ttf", lineHeightPixels=180'f32)
   var textbox = initText(32, font, "", color=newVec4f(1, 0, 0, 1))
-  let fontscale = 0.005
+  let fontscale = 0.001
   scene.add textbox
   textbox.mesh.transform = scale(fontscale, fontscale)
   engine.loadScene(scene)