Skip to content

Commit 9a247ad

Browse files
vector graphics via atlas :)
1 parent 4690bd8 commit 9a247ad

File tree

10 files changed

+275
-40
lines changed

10 files changed

+275
-40
lines changed

Flamui/Drawing/GlCanvas.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,14 @@ public class GlCanvas
9292
private readonly Renderer _renderer;
9393
public Paint Paint;
9494
private static Dictionary<Bitmap, GpuTexture> _textures = []; //Todo not static
95+
private static VgAtlas _vgAtlas;
9596

9697
public GlCanvas(Renderer renderer, Arena arena)
9798
{
9899
_renderer = renderer;
99100
MeshBuilder = new MeshBuilder(arena);
100101

102+
_vgAtlas = new VgAtlas(renderer);
101103
Start();
102104
}
103105

@@ -123,6 +125,23 @@ public void DrawBitmap(Bitmap bitmap, Bounds bounds)
123125
MeshBuilder.AddTriangle(bottomRight, bottomLeft, topLeft);
124126
}
125127

128+
public void DrawTinyVG(int vgId, Slice<byte> tinyVG, Bounds bounds)
129+
{
130+
var resolutionMultiplier = new Vector2(1, 1).Multiply(MeshBuilder.Matrix.GetScale()).Y;
131+
var entry = _vgAtlas.GetAtlasEntry(vgId, tinyVG.Span, (uint)(bounds.W * resolutionMultiplier),
132+
(uint)(bounds.H * resolutionMultiplier));
133+
134+
var entryBounds = new Bounds(new Vector2(entry.X, entry.Y) / 1000, new Vector2(entry.Width, entry.Height) / 1000);
135+
136+
uint topLeft = MeshBuilder.AddVertex(bounds.TopLeft(), entryBounds.TopLeft(), Paint.Color, textureType: TextureType.Texture, texture: _vgAtlas.GpuTexture);
137+
uint topRight = MeshBuilder.AddVertex(bounds.TopRight(), entryBounds.TopRight(), Paint.Color, textureType: TextureType.Texture, texture: _vgAtlas.GpuTexture);
138+
uint bottomRight = MeshBuilder.AddVertex(bounds.BottomRight(), entryBounds.BottomRight(), Paint.Color, textureType: TextureType.Texture, texture: _vgAtlas.GpuTexture);
139+
uint bottomLeft = MeshBuilder.AddVertex(bounds.BottomLeft(), entryBounds.BottomLeft(), Paint.Color, textureType: TextureType.Texture, texture: _vgAtlas.GpuTexture);
140+
141+
MeshBuilder.AddTriangle(topLeft, topRight, bottomRight);
142+
MeshBuilder.AddTriangle(bottomRight, bottomLeft, topLeft);
143+
}
144+
126145
public void DrawText(ReadOnlySpan<char> text, float x, float y)
127146
{
128147
var resolutionMultiplier = new Vector2(1, 1).Multiply(MeshBuilder.Matrix.GetScale()).Y;

Flamui/Drawing/TinyVG.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using System.Runtime.InteropServices;
1+
using System.Runtime.CompilerServices;
2+
using System.Runtime.InteropServices;
23

34
namespace Flamui.Drawing;
45

6+
57
public enum TinyvgError : int
68
{
79
Success = 0,
@@ -59,4 +61,30 @@ ref TinyvgBitmap bitmap
5961

6062
[DllImport("tinyvg.dll", CallingConvention = CallingConvention.Cdecl)]
6163
public static extern void tinyvg_free_bitmap(ref TinyvgBitmap bitmap);
64+
65+
//https://tinyvg.tech/download/specification.pdf
66+
public static (float width, float height) ParseHeader(Span<byte> vgFile)
67+
{
68+
if (!BitConverter.IsLittleEndian)
69+
throw new Exception("Not supported on this system");
70+
71+
if (vgFile[0] != 0x72 || vgFile[1] != 0x56 || vgFile[2] != 1)
72+
throw new Exception("Invalid Header");
73+
74+
var b = vgFile[3];
75+
var scale = (b & 0b00001111) >> 0;
76+
var colorEncoding = (b & 0b00110000) >> 4;
77+
var coordinateRange = (b & 0b11000000) >> 6;
78+
79+
if (colorEncoding != 0)
80+
throw new Exception("Only rgba supported");
81+
82+
if (coordinateRange != 0)
83+
throw new Exception("Only support 16 bit coordinate ranges");
84+
85+
var width = (float)BitConverter.ToUInt16(vgFile.Slice(4)) / scale;
86+
var height = (float)BitConverter.ToUInt16(vgFile.Slice(6)) / scale;
87+
88+
return (width, height);
89+
}
6290
}

Flamui/RenderContext.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ public void AddBitmap(Bitmap bitmap, Bounds bounds)
8585
Add(cmd);
8686
}
8787

88+
public void AddVectorGraphics(int vgId, Slice<byte> vgData, Bounds bounds)
89+
{
90+
var cmd = new Command();
91+
cmd.Type = CommandType.TinyVG;
92+
cmd.Bounds = bounds;
93+
cmd.VGId = vgId;
94+
cmd.VGData = vgData;
95+
96+
Add(cmd);
97+
}
98+
8899
/// <summary>
89100
/// Multiplies the current matrix with the new matrix
90101
/// </summary>
@@ -234,6 +245,9 @@ public void Rerender(Renderer renderer)
234245
case CommandType.Picture:
235246
canvas.DrawBitmap(command.Bitmap, command.Bounds);
236247
break;
248+
case CommandType.TinyVG:
249+
canvas.DrawTinyVG(command.VGId, command.VGData, command.Bounds);
250+
break;
237251
default:
238252
throw new ArgumentOutOfRangeException(command.Type.ToString());
239253
}

Flamui/Renderable.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Diagnostics.CodeAnalysis;
22
using System.Diagnostics.Contracts;
33
using System.Numerics;
4+
using System.Runtime.CompilerServices;
45
using System.Runtime.InteropServices;
56
using Flamui.Drawing;
67
using Flamui.UiElements;
@@ -53,6 +54,15 @@ public Bounds(Vector2 position, Vector2 size)
5354
H = size.Y;
5455
}
5556

57+
[SetsRequiredMembers]
58+
public Bounds(float x, float y, float width, float height)
59+
{
60+
X = x;
61+
Y = y;
62+
W = width;
63+
H = height;
64+
}
65+
5666
public Vector2 GetPosition()
5767
{
5868
return new Vector2(X, Y);
@@ -78,9 +88,13 @@ public bool ContainsPoint(Vector2 point)
7888
return withinX && withinY;
7989
}
8090

91+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8192
public Vector2 TopLeft() => new(X, Y);
93+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8294
public Vector2 TopRight() => new(X + W, Y);
95+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8396
public Vector2 BottomLeft() => new(X, Y + H);
97+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8498
public Vector2 BottomRight() => new(X + W, Y + H);
8599

86100
//
@@ -113,7 +127,8 @@ public enum CommandType : byte
113127
Text,
114128
Matrix,
115129
Path,
116-
Picture
130+
Picture,
131+
TinyVG
117132
}
118133

119134
public struct Command : IEquatable<Command>
@@ -129,6 +144,8 @@ public struct Command : IEquatable<Command>
129144
public ColorDefinition Color;
130145
public Matrix4X4<float> Matrix;
131146
public Bitmap Bitmap;
147+
public int VGId;
148+
public Slice<byte> VGData;
132149

133150
public bool Equals(Command other)
134151
{

Flamui/Slice.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Flamui;
55

6-
public unsafe struct Slice<T> : IEnumerable<T> where T : unmanaged
6+
public unsafe struct Slice<T> : IEnumerable<T>, IEquatable<Slice<T>> where T : unmanaged
77
{
88
public T* Items;
99
public int Length;
@@ -19,7 +19,7 @@ public Slice(T* items, int length)
1919

2020
public void MemZero()
2121
{
22-
Unsafe.InitBlock(Items, 0, (uint)(sizeof(int) * Length));
22+
Unsafe.InitBlock(Items, 0, (uint)(sizeof(T) * Length));
2323
}
2424

2525
public override string ToString()
@@ -72,7 +72,7 @@ public ref T this[int index]
7272

7373
public override int GetHashCode()
7474
{
75-
return HashCode.Combine(Items->GetHashCode(), Length.GetHashCode());
75+
return HashCode.Combine(unchecked((int)(long)Items), Length);
7676
}
7777

7878
public Enumerator GetEnumerator()
@@ -127,4 +127,24 @@ public void Reset()
127127
_nextIndex = default;
128128
}
129129
}
130+
131+
public bool Equals(Slice<T> other)
132+
{
133+
return Items == other.Items && Length == other.Length;
134+
}
135+
136+
public override bool Equals(object? obj)
137+
{
138+
return obj is Slice<T> other && Equals(other);
139+
}
140+
141+
public static bool operator ==(Slice<T> left, Slice<T> right)
142+
{
143+
return left.Equals(right);
144+
}
145+
146+
public static bool operator !=(Slice<T> left, Slice<T> right)
147+
{
148+
return !(left == right);
149+
}
130150
}

Flamui/TGALoader.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Diagnostics;
2+
using System.Diagnostics.Contracts;
3+
using System.Runtime.CompilerServices;
24
using System.Runtime.InteropServices;
35
using System.Text;
46
using System.Text.Json;
@@ -22,6 +24,51 @@ public override int GetHashCode()
2224
{
2325
return HashCode.Combine(Width.GetHashCode(), Height.GetHashCode(), Data.GetHashCode());
2426
}
27+
28+
public int Stride()
29+
{
30+
return BitmapFormat switch
31+
{
32+
BitmapFormat.R => 1 * (int)Width,
33+
BitmapFormat.RGBA => 4 * (int)Width,
34+
_ => throw new ArgumentOutOfRangeException()
35+
};
36+
}
37+
38+
public void CopyTo(Bitmap bitmap)
39+
{
40+
if (Width > bitmap.Width || Height > bitmap.Height)
41+
throw new Exception("Bitmap is too large :(");
42+
43+
bitmap.Data.MemZero();
44+
45+
var stride = Stride();
46+
47+
for (int y = 0; y < Height; y++)
48+
{
49+
Data.Span.Slice(y * stride, stride).CopyTo(bitmap.Data.Span.Slice(y * bitmap.Stride(), stride));
50+
}
51+
}
52+
53+
public readonly void PrintToConsole()
54+
{
55+
for (int i = 0; i < Height; i++)
56+
{
57+
for (int j = 0; j < Width; j++)
58+
{
59+
if (BitmapFormat == BitmapFormat.RGBA)
60+
{
61+
var val1 = Data[(int)((i * Width + j) * 4)];
62+
var val2 = Data[(int)((i * Width + j) * 4) + 1];
63+
var val3 = Data[(int)((i * Width + j) * 4) + 2];
64+
var val4 = Data[(int)((i * Width + j) * 4) + 3];
65+
var fin = val1 + val2 + val3;
66+
Console.Write($"{fin,3}");
67+
}
68+
}
69+
Console.WriteLine();
70+
}
71+
}
2572
}
2673

2774
public class TGALoader

Flamui/UiElements/UiSvg.cs

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Numerics;
2+
using System.Runtime.InteropServices;
23
using Flamui.Drawing;
34
using Flamui.Layouting;
45
using Point = Flamui.Layouting.Point;
@@ -8,16 +9,15 @@ namespace Flamui.UiElements;
89
//Todo, we should probably offload svg loading onto a separate thread, or do them async etc., or maybe preload them
910
public class UiSvg : UiElement
1011
{
11-
private float factor;
1212
public string Src { get; set; } = null!;
1313
public ColorDefinition? ColorDefinition { get; set; }
14-
private static readonly Dictionary<string, Bitmap> SSvgCache = new();
14+
private static readonly Dictionary<string, (Slice<byte>, float aspectRatio)> SSvgCache = new();
1515

1616
public override void Render(RenderContext renderContext, Point offset)
1717
{
1818
var bitmap = GetBitmap();
1919

20-
renderContext.AddBitmap(bitmap, new Bounds(new Vector2(offset.X, offset.Y), new Vector2(bitmap.Width, bitmap.Height) * factor));
20+
renderContext.AddVectorGraphics(Src.GetHashCode(), bitmap.Item1, new Bounds(offset.X, offset.Y, Rect.Width, Rect.Height));
2121
}
2222

2323
public override void PrepareLayout(Dir dir)
@@ -31,54 +31,40 @@ public override void PrepareLayout(Dir dir)
3131

3232
public override BoxSize Layout(BoxConstraint constraint)
3333
{
34-
var svg = GetBitmap();
35-
36-
var svgRatio = svg.Width / svg.Height;
34+
var (_, svgRatio) = GetBitmap();
3735

3836
//try to be as big as possible given the constraints
3937
var availableRatio = constraint.MaxWidth / constraint.MaxHeight;
4038

4139
if (availableRatio > svgRatio) //Height is the limiting factor
4240
{
43-
factor = constraint.MaxHeight / svg.Height;
41+
Rect = new BoxSize(constraint.MaxHeight * svgRatio, constraint.MaxHeight);
4442
}
4543
else //Width is the limiting factor
4644
{
47-
factor = constraint.MaxWidth / svg.Width;
45+
Rect = new BoxSize(constraint.MaxWidth, constraint.MaxWidth / svgRatio);
4846
}
4947

50-
Rect = new BoxSize(svg.Width * factor, svg.Height * factor);
5148
return Rect;
5249
}
5350

5451

55-
private unsafe Bitmap GetBitmap()
52+
private unsafe (Slice<byte>, float aspectRatio) GetBitmap()
5653
{
57-
if (!SSvgCache.TryGetValue(Src, out var bitmap))
54+
if (!SSvgCache.TryGetValue(Src, out var entry))
5855
{
5956
var bytes = File.ReadAllBytes(Src);
6057

61-
fixed (byte* tvg = bytes)
62-
{
63-
TinyvgBitmap tinyvgBitmap = default;
64-
var err = TinyVG.tinyvg_render_bitmap(tvg, bytes.Length, TinyvgAntiAlias.X16, 100, 100, ref tinyvgBitmap);
65-
if (err != TinyvgError.Success)
66-
{
67-
throw new Exception($"Error rendering svg: {err}");
68-
}
58+
var (width, height) = TinyVG.ParseHeader(bytes);
6959

70-
bitmap = new Bitmap
71-
{
72-
Width = tinyvgBitmap.Width,
73-
Height = tinyvgBitmap.Height,
74-
Data = new Slice<byte>((byte*)tinyvgBitmap.Pixels, (int)(tinyvgBitmap.Width * tinyvgBitmap.Height * 4)),
75-
BitmapFormat = BitmapFormat.RGBA
76-
};
77-
}
60+
var ptr = (byte*)Marshal.AllocHGlobal(bytes.Length);
61+
var dest = new Span<byte>(ptr, bytes.Length);
62+
bytes.AsSpan().CopyTo(dest);
7863

79-
SSvgCache.Add(Src, bitmap);
64+
entry = (new Slice<byte>(ptr, bytes.Length), (float)width / (float)height);
65+
SSvgCache.Add(Src, entry);
8066
}
8167

82-
return bitmap;
68+
return entry;
8369
}
8470
}

0 commit comments

Comments
 (0)