diff --git a/Quik.FreeType/Structures.cs b/Quik.FreeType/Structures.cs
index 046b38f..6a4eca8 100644
--- a/Quik.FreeType/Structures.cs
+++ b/Quik.FreeType/Structures.cs
@@ -1,6 +1,9 @@
using System;
using System.Runtime.InteropServices;
+// Disable unused warnings for native types.
+#pragma warning disable CS0649
+
namespace Quik.FreeType
{
public struct FTLibrary
diff --git a/Quik.Media.Defaults/FTProvider.cs b/Quik.Media.Defaults/FTProvider.cs
new file mode 100644
index 0000000..fd3a290
--- /dev/null
+++ b/Quik.Media.Defaults/FTProvider.cs
@@ -0,0 +1,16 @@
+using System;
+using Quik.FreeType;
+
+namespace Quik.Media.Defaults
+{
+ public static class FTProvider
+ {
+ private static FTLibrary _ft;
+ public static FTLibrary Ft => _ft;
+
+ static FTProvider()
+ {
+ FT.InitFreeType(out _ft);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Quik.Media.Defaults/QFontFreeType.cs b/Quik.Media.Defaults/QFontFreeType.cs
new file mode 100644
index 0000000..a92dbab
--- /dev/null
+++ b/Quik.Media.Defaults/QFontFreeType.cs
@@ -0,0 +1,108 @@
+using System;
+using System.IO;
+using Quik.FreeType;
+using Quik.Media;
+using Quik.Media.Color;
+
+namespace Quik.Media.Defaults
+{
+ public class QFontFreeType : QFont
+ {
+ private MemoryStream ms;
+ private FTFace face;
+
+ public override FontInfo Info => throw new NotImplementedException();
+
+ public QFontFreeType(Stream stream)
+ {
+ ms = new MemoryStream();
+ stream.CopyTo(ms);
+
+ FTError e = FT.NewMemoryFace(Ft, ms.GetBuffer(), ms.Length, 0, out face);
+ if (e != FTError.None)
+ {
+ throw new Exception("Could not load font face from stream.");
+ }
+ }
+
+ public override bool HasRune(int rune)
+ {
+ return FT.GetCharIndex(face, (ulong)rune) != 0;
+ }
+
+ public override QFontPage RasterizePage(int codepage, float size, in FontRasterizerOptions options)
+ {
+ FT.SetCharSize(face, 0, (long)Math.Round(64*size), 0, (uint)Math.Round(options.Resolution));
+ QGlyphMetrics[] allMetrics = new QGlyphMetrics[256];
+
+ // Figure out the map size needed.
+ int pixels = 0;
+ for (int i = 0; i < 256; i++)
+ {
+ uint index = FT.GetCharIndex(face, (ulong)(codepage + i));
+ FT.LoadGlyph(face, index, FTLoadFlags.Default);
+
+ ref readonly FTGlyphMetrics metrics = ref face.Glyph.Metrics;
+ allMetrics[i] = new QGlyphMetrics(
+ codepage + i,
+ new QVec2(metrics.Width, metrics.Height),
+ new QVec2(metrics.HorizontalBearingX/64f, metrics.HorizontalBearingY/64f),
+ new QVec2(metrics.VerticalBearingX/64f, metrics.VerticalBearingY/64f),
+ new QVec2(metrics.HorizontalAdvance/64f, metrics.VerticalAdvance/64f)
+ );
+ pixels = (int)Math.Max(pixels, Math.Max(face.Glyph.Metrics.Width/64f, face.Glyph.Metrics.Height/64f));
+ }
+
+ int bits = Math.ILogB(pixels);
+ if (1 << bits != pixels)
+ {
+ pixels = 1 << bits + 1;
+ }
+
+ // Now we can create a bitmap and render our glyphs.
+
+ QImageBuffer buffer = new QImageBuffer(QImageFormat.RedU8, (int)pixels, (int)pixels, 256);
+
+ for (int i = 0; i < 256; i++)
+ {
+ uint index = FT.GetCharIndex(face, (ulong)(codepage + i));
+ FT.LoadGlyph(face, index, FTLoadFlags.Default);
+ FT.RenderGlyph(face.Glyph, options.Sdf ? FTRenderMode.Sdf : FTRenderMode.Mono);
+
+ ref readonly FTBitmap bitmap = ref face.Glyph.Bitmap;
+
+ buffer.LockBits3d(out QImageLock dst, QImageLockOptions.Default, i);
+
+ if (bitmap.Buffer != IntPtr.Zero) unsafe
+ {
+ for (int j = 0; j < bitmap.Rows; j++)
+ {
+ Buffer.MemoryCopy(
+ (byte*)bitmap.Buffer + (j * bitmap.Pitch),
+ (byte*)dst.ImagePtr + (j * dst.Width),
+ dst.Width,
+ bitmap.Width);
+ }
+ }
+
+ buffer.UnlockBits();
+ }
+
+ return new QFontPage(this, codepage, size, options, buffer, allMetrics);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ ms.Dispose();
+ }
+
+ FT.DoneFace(face);
+ }
+
+ private static FTLibrary Ft => FTProvider.Ft;
+ }
+}
\ No newline at end of file
diff --git a/Quik.Media.Defaults/QImageStbi.cs b/Quik.Media.Defaults/QImageStbi.cs
index cbcd319..06207d4 100644
--- a/Quik.Media.Defaults/QImageStbi.cs
+++ b/Quik.Media.Defaults/QImageStbi.cs
@@ -10,7 +10,7 @@ namespace Quik.Media.Defaults
public unsafe class QImageStbi : QImage
{
private readonly StbImage image;
- private ImageBuffer buffer;
+ private QImageBuffer buffer;
public override int Width => image.Width;
@@ -47,8 +47,8 @@ namespace Quik.Media.Defaults
if (options.MipLevel > 0) throw new Exception("This image has no mip levels.");
buffer?.Dispose();
- buffer = new ImageBuffer(options.Format, Width, Height);
- QImageLock dst = buffer.Lock();
+ buffer = new QImageBuffer(options.Format, Width, Height);
+ buffer.LockBits2d(out QImageLock dst, QImageLockOptions.Default);
byte *srcPtr = (byte*)image.ImagePointer;
QImageLock src = new QImageLock(InternalFormat, Width, Height, 1, (IntPtr)srcPtr);
@@ -76,7 +76,7 @@ namespace Quik.Media.Defaults
public override void UnlockBits()
{
- buffer.Unlock();
+ buffer.UnlockBits();
}
protected override void Dispose(bool disposing)
diff --git a/Quik.Media.Defaults/Quik.Media.Defaults.csproj b/Quik.Media.Defaults/Quik.Media.Defaults.csproj
index 75adbf8..0868f36 100644
--- a/Quik.Media.Defaults/Quik.Media.Defaults.csproj
+++ b/Quik.Media.Defaults/Quik.Media.Defaults.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/Quik.OpenTK/Quik.OpenTK.csproj b/Quik.OpenTK/Quik.OpenTK.csproj
index 977354d..16f725e 100644
--- a/Quik.OpenTK/Quik.OpenTK.csproj
+++ b/Quik.OpenTK/Quik.OpenTK.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/Quik/Media/Color/ImageBuffer.cs b/Quik/Media/Color/ImageBuffer.cs
index 9c9dfb0..44b861d 100644
--- a/Quik/Media/Color/ImageBuffer.cs
+++ b/Quik/Media/Color/ImageBuffer.cs
@@ -1,60 +1,92 @@
using System;
using System.Runtime.InteropServices;
-namespace Quik.Media.Color
+namespace Quik.Media
{
- public class ImageBuffer : IDisposable
+ public class QImageBuffer : QImage
{
private byte[] buffer;
GCHandle handle;
- public QImageFormat Format { get; }
- public int Width { get; }
- public int Height { get; }
- public int Depth { get; }
+ public override QImageFormat InternalFormat { get; }
+ public override int Width { get; }
+ public override int Height { get; }
+ public override int Depth { get; }
- public ImageBuffer(QImageFormat format, int width, int height, int depth = 1)
+ public QImageBuffer(QImageFormat format, int width, int height, int depth = 1)
{
- Format = format;
+ InternalFormat = format;
Width = width;
Height = height;
Depth = depth;
- buffer = new byte[width * height * depth];
+ buffer = new byte[width * height * depth * format.BytesPerPixel()];
}
- ~ImageBuffer()
+ ~QImageBuffer()
{
Dispose(false);
}
- public QImageLock Lock()
+ private QImageLock Lock()
{
handle.Free();
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
- return new QImageLock(Format, Width, Height, Depth, ptr);
+ return new QImageLock(InternalFormat, Width, Height, Depth, ptr);
}
- public void Unlock()
+ protected override void Dispose(bool disposing)
{
- handle.Free();
- }
-
- private bool isDiposed = false;
- private void Dispose(bool disposing)
- {
- if (isDiposed) return;
-
+ if (handle.IsAllocated) handle.Free();
buffer = null;
- handle.Free();
- isDiposed = true;
GC.SuppressFinalize(this);
}
- public void Dispose()
+ public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
{
- Dispose(true);
+ if (options.Format != options.Format) throw new InvalidOperationException("This image type cannot be converted.");
+ if (Depth > 1) throw new InvalidOperationException("This texture has a depth component.");
+
+ UnlockBits();
+
+ handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+ IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
+ imageLock = new QImageLock(InternalFormat, Width, Height, Depth, ptr);
+ }
+
+ public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
+ {
+ if (options.Format != options.Format) throw new InvalidOperationException("This image type cannot be converted.");
+ UnlockBits();
+
+ handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+ IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
+ imageLock = new QImageLock(InternalFormat, Width, Height, Depth, ptr);
+ }
+
+ public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
+ {
+ if (options.Format != options.Format) throw new InvalidOperationException("This image type cannot be converted.");
+ if (depth < 0 || depth > Depth)
+ throw new ArgumentOutOfRangeException(nameof(depth), "Depth must be in range.");
+
+ UnlockBits();
+
+ handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+ IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
+ imageLock = new QImageLock(
+ InternalFormat,
+ Width,
+ Height,
+ 1,
+ ptr + (depth * Width * Height * InternalFormat.BytesPerPixel()));
+ }
+
+ public override void UnlockBits()
+ {
+ if (handle.IsAllocated)
+ handle.Free();
}
}
}
\ No newline at end of file
diff --git a/Quik/Media/Extensions.cs b/Quik/Media/Extensions.cs
index 64cff5f..8c06d6c 100644
--- a/Quik/Media/Extensions.cs
+++ b/Quik/Media/Extensions.cs
@@ -32,7 +32,7 @@ namespace Quik.Media
}
}
- public static int BitsPerPixel(this QImageFormat format)
+ public static int BytesPerPixel(this QImageFormat format)
{
switch (format)
{
diff --git a/Quik/Media/FontInfo.cs b/Quik/Media/FontInfo.cs
index 73632e6..cdc3653 100644
--- a/Quik/Media/FontInfo.cs
+++ b/Quik/Media/FontInfo.cs
@@ -6,31 +6,27 @@ namespace Quik.Media
{
public string Family { get; }
public FontStyle Style { get; }
- public float Size { get; }
public override string ToString()
{
- return $"{Family} {Style} {Size}";
+ return $"{Family} {Style}";
}
public override int GetHashCode()
{
return Family.GetHashCode() ^
- (Style.GetHashCode() * 3976061) ^
- (Size.GetHashCode() * 9428791);
+ (Style.GetHashCode() * 3976061);
}
public static bool operator==(FontInfo a, FontInfo b)
{
return (a.Style == b.Style) &&
- (a.Size == b.Size) &&
(a.Family == a.Family);
}
public static bool operator!=(FontInfo a, FontInfo b)
{
return (a.Style != b.Style) ||
- (a.Size != b.Size) ||
(a.Family != b.Family);
}
diff --git a/Quik/Media/ImageBuffer.cs b/Quik/Media/ImageBuffer.cs
new file mode 100644
index 0000000..2a73040
--- /dev/null
+++ b/Quik/Media/ImageBuffer.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Quik.Media.Color
+{
+ public class QImageBuffer : QImage
+ {
+ private byte[] buffer;
+ GCHandle handle;
+
+ public override QImageFormat InternalFormat { get; }
+ public override int Width { get; }
+ public override int Height { get; }
+ public override int Depth { get; }
+
+ public QImageBuffer(QImageFormat format, int width, int height, int depth = 1)
+ {
+ InternalFormat = format;
+ Width = width;
+ Height = height;
+ Depth = depth;
+
+ buffer = new byte[width * height * depth];
+ }
+ ~QImageBuffer()
+ {
+ Dispose(false);
+ }
+
+ private QImageLock Lock()
+ {
+ handle.Free();
+ handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
+ IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
+ return new QImageLock(InternalFormat, Width, Height, Depth, ptr);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ buffer = null;
+ handle.Free();
+
+ GC.SuppressFinalize(this);
+ }
+
+ public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
+ {
+ imageLock = Lock();
+ }
+
+ public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
+ {
+ imageLock = Lock();
+ }
+
+ public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
+ {
+ imageLock = Lock();
+ }
+
+ public override void UnlockBits()
+ {
+ handle.Free();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Quik/Media/QFont.cs b/Quik/Media/QFont.cs
index 33295ad..493aa94 100644
--- a/Quik/Media/QFont.cs
+++ b/Quik/Media/QFont.cs
@@ -10,12 +10,10 @@ namespace Quik.Media
public abstract FontInfo Info { get; }
public string Family => Info.Family;
public FontStyle Style => Info.Style;
- public float Size => Info.Size;
public abstract bool HasRune(int rune);
- public abstract QImage RenderPage(int codepage, float resolution, bool sdf);
- public abstract QGlyphMetrics GetMetricsForRune(int rune);
- public abstract QGlyphMetrics[] GetMetricsForPage(int codepage);
+ public abstract QFontPage RasterizePage(int codepage, float size, in FontRasterizerOptions options);
+ public QFontPage RasterizePage(int codepage, float size) => RasterizePage(codepage, size, FontRasterizerOptions.Default);
// IDisposable
private bool isDisposed = false;
@@ -30,4 +28,62 @@ namespace Quik.Media
protected virtual void Dispose(bool disposing) { }
public void Dispose() => DisposePrivate(true);
}
+
+ public struct FontRasterizerOptions
+ {
+ public float Resolution { get; set; }
+ public bool Sdf { get; set; }
+
+ public static readonly FontRasterizerOptions Default = new FontRasterizerOptions()
+ {
+ Resolution = 96.0f,
+ Sdf = true
+ };
+ }
+
+ public class QFontPage : IDisposable
+ {
+ public QFont Font { get; }
+ public int CodePage { get; }
+ public float Size { get; }
+ public virtual QImage Image { get; } = null;
+ public virtual QGlyphMetrics[] Metrics { get; } = Array.Empty();
+
+ public FontRasterizerOptions Options { get; }
+ public float Resolution => Options.Resolution;
+ public bool Sdf => Options.Sdf;
+
+ public void Dispose() => DisposeInternal(false);
+
+ protected QFontPage(QFont font, int codepage, float size, in FontRasterizerOptions options)
+ {
+ Font = font;
+ CodePage = codepage;
+ Size = size;
+ Options = options;
+ }
+ public QFontPage(QFont font, int codepage, float size, in FontRasterizerOptions options, QImage image, QGlyphMetrics[] metrics)
+ : this(font, codepage, size, options)
+ {
+ Image = image;
+ Metrics = metrics;
+ }
+
+ private bool isDisposed = false;
+ private void DisposeInternal(bool disposing)
+ {
+ if (isDisposed) return;
+
+ Dispose(disposing);
+ isDisposed = true;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Image?.Dispose();
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/Quik/Media/QGlyphMetrics.cs b/Quik/Media/QGlyphMetrics.cs
index 3b4538a..84a5872 100644
--- a/Quik/Media/QGlyphMetrics.cs
+++ b/Quik/Media/QGlyphMetrics.cs
@@ -10,10 +10,10 @@ namespace Quik.Media
///
public int Rune { get; }
- ///
- /// Location of the glyph on the atlas.
- ///
- public QRectangle Location { get; }
+ // ///
+ // /// Location of the glyph on the atlas.
+ // ///
+ // public QRectangle Location { get; }
///
/// Size of the glyph in units.
@@ -37,14 +37,14 @@ namespace Quik.Media
public QGlyphMetrics(
int character,
- QRectangle location,
+ // QRectangle location,
QVec2 size,
QVec2 horizontalBearing,
QVec2 verticalBearing,
QVec2 advance)
{
Rune = character;
- Location = location;
+ // Location = location;
Size = size;
HorizontalBearing = horizontalBearing;
VerticalBearing = verticalBearing;