From 3484dce8c5644536a44f4d3c0e54a9d79eafba1f Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Wed, 1 May 2024 16:53:30 +0300 Subject: [PATCH] Create a font atlaser. --- Quik/Media/Font/FontAtlas.cs | 158 +++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 Quik/Media/Font/FontAtlas.cs diff --git a/Quik/Media/Font/FontAtlas.cs b/Quik/Media/Font/FontAtlas.cs new file mode 100644 index 0000000..1900748 --- /dev/null +++ b/Quik/Media/Font/FontAtlas.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; + +namespace Quik.Media.Font +{ + public struct FontAtlasGlyphInfo + { + public int Codepoint; + public QImage Image; + public QRectangle UVs; + } + + public class FontAtlas + { + private readonly int width, height; + private readonly List atlases = new List(); + private readonly Dictionary glyphs = new Dictionary(); + private int index = 0; + private AtlasPage last = null; + + public FontAtlas(int width, int height) + { + this.width = width; + this.height = height; + } + + public bool GetGlyph(int codepoint, out FontAtlasGlyphInfo info) + { + return glyphs.TryGetValue(codepoint, out info); + } + + public void PutGlyph(int codepoint, QImageLock source, out FontAtlasGlyphInfo info) + { + info = new FontAtlasGlyphInfo() { Codepoint = codepoint }; + + if (last == null || last.IsFull) + { + AddPage(); + } + + last.PutGlyph(source, ref info); + } + + private void AddPage() + { + index++; + + if (index < atlases.Count) + { + last = atlases[index]; + } + else + { + last = new AtlasPage(width, height); + atlases.Add(last); + } + } + + public void Clear() + { + // Trim any pages that were not used yet. + for (int i = atlases.Count -1; i >= 0; i--) + { + if (atlases[i].PointerX != 0 && atlases[i].PointerY != 0) + { + for (int j = i + 1; j < atlases.Count; j++) + { + atlases[j].Dispose(); + } + + if (i != atlases.Count - 1) + atlases.RemoveRange(i+1, atlases.Count - i - 1); + break; + } + } + + if (atlases.Count > 0) + { + last = atlases[0]; + } + else + { + last = null; + } + index = -1; + + glyphs.Clear(); + } + + private class AtlasPage : IDisposable + { + public QImage Image; + public int PointerX, PointerY; + public int RowHeight; + + public bool IsFull => PointerX > Image.Width || PointerY > Image.Height; + + public AtlasPage(int width, int height) + { + Image = new QImageBuffer(QImageFormat.RedU8, width, height); + Reset(); + } + + public void PutGlyph(QImageLock src, ref FontAtlasGlyphInfo prototype) + { + if (IsFull) + throw new Exception("Page is full!"); + + Image.LockBits2d(out QImageLock dst, QImageLockOptions.Default); + src.CopyTo(dst, PointerX, PointerY); + Image.UnlockBits(); + + QVec2 min = new QVec2(PointerX/Image.Width, PointerY/Image.Width); + QVec2 size = new QVec2(src.Width/Image.Width, src.Height/Image.Height); + + prototype.Image = Image; + prototype.UVs = new QRectangle(min + size, min); + + AdvanceColumn(src.Width, src.Height); + } + + public void Reset() + { + RowHeight = PointerX = PointerY = 0; + } + + public void AdvanceRow() + { + PointerX = 0; + PointerY += RowHeight; + + RowHeight = 0; + } + + public void AdvanceColumn(int width, int height) + { + RowHeight = Math.Max(RowHeight, height); + PointerX += width; + + if (PointerX > Image.Width) + { + AdvanceRow(); + } + } + + private bool isDisposed = false; + public void Dispose() + { + if (isDisposed) + return; + + Image?.Dispose(); + + isDisposed = true; + } + } + } +} \ No newline at end of file