Compare commits

...

2 Commits

Author SHA1 Message Date
3484dce8c5 Create a font atlaser. 2024-05-01 16:53:30 +03:00
3418537b43 Add image copy functions to QImage. 2024-05-01 16:52:20 +03:00
2 changed files with 213 additions and 1 deletions

View File

@ -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<AtlasPage> atlases = new List<AtlasPage>();
private readonly Dictionary<int, FontAtlasGlyphInfo> glyphs = new Dictionary<int, FontAtlasGlyphInfo>();
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;
}
}
}
}

View File

@ -1,5 +1,4 @@
using System;
namespace Quik.Media
{
public abstract class QImage : IDisposable
@ -62,5 +61,60 @@ namespace Quik.Media
Depth = depth;
ImagePtr = ptr;
}
public unsafe void CopyTo(QImageLock destination, int x, int y)
{
if (
Width + x > destination.Width ||
Height + y > destination.Height)
{
throw new Exception("Image falls outside the bounds of the destination.");
}
else if (Format != destination.Format)
{
throw new Exception("Image formats must be the same.");
}
int bpp = Format.BytesPerPixel();
for (int i = 0; i < Height; i++)
{
IntPtr srcPtr = (IntPtr)((long)ImagePtr + i * bpp);
long dstPos = x + i * destination.Width;
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
Buffer.MemoryCopy((void*)srcPtr, (void*)dstPtr, Width * bpp, Width * bpp);
}
}
public unsafe void ExtractFrom(QImageLock destination, int x, int y, int width, int height)
{
if (
width != destination.Width ||
height != destination.Height)
{
throw new Exception("Destination is not the same size as the subregion.");
}
else if (x + width > Width || y + height > Height)
{
throw new Exception("The subregion is larger than this image.");
}
else if (Format != destination.Format)
{
throw new Exception("Image formats must be the same.");
}
int bpp = Format.BytesPerPixel();
for (int i = 0; i < height; i++)
{
long srcPos = x + y * i;
IntPtr srcPtr = (IntPtr)((long)ImagePtr + srcPos * bpp);
long dstPos = i * destination.Width;
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
Buffer.MemoryCopy((void*)srcPtr, (void*)dstPtr, width * bpp, width * bpp);
}
}
}
}